├── C64 Hires Check.lua ├── C64 Multicolor Check.lua ├── Export Hires.lua ├── Export Koala.lua ├── Import Koala.lua ├── exporthires_sshot.png ├── exportkoala_sshot.png ├── hirescheck_sshot.png ├── hiresstarter.aseprite ├── hiresview.prg ├── importkoala_sshot.png ├── koalaview.prg ├── multicheck_sshot.png ├── readme.md └── starter.aseprite /C64 Hires Check.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | -- 3 | -- C64 hires bitmap format checker 4 | -- 5 | ---------------------------------------------------------------------- 6 | 7 | 8 | local actLayer = nil 9 | -- Forward declarations 10 | local d 11 | local buildWindow = function() 12 | end 13 | 14 | 15 | ---------------------------------------------------------------------- 16 | -- HELPER FUNCTIONS 17 | ---------------------------------------------------------------------- 18 | 19 | local function addToSet(set, key) 20 | if set[key] == nil then 21 | set[key] = 1 22 | else 23 | set[key] = set[key]+1 24 | end 25 | end 26 | 27 | local function removeFromSet(set, key) 28 | set[key] = nil 29 | end 30 | 31 | local function setContains(set, key) 32 | return set[key] ~= nil 33 | end 34 | 35 | local function setCount(set) 36 | local count = 0 37 | for i in pairs(set) do 38 | count = count + 1 39 | end 40 | return count 41 | end 42 | 43 | 44 | 45 | local function checkPrerequesities(app) 46 | local spr = app.activeSprite 47 | if not spr then 48 | app.alert("There is no active sprite!") 49 | return false 50 | end 51 | 52 | if spr.width~=320 or spr.height ~= 200 then 53 | app.alert("The sprite dimensions should be 320x200!") 54 | return false 55 | end 56 | 57 | if spr.colorMode~= ColorMode.INDEXED then 58 | app.alert("The sprite should use indexed color mode!") 59 | return false 60 | end 61 | 62 | -- TODO: 63 | -- colors check: 64 | -- check actual colors against a built in palette? 65 | -- just check the number of colors > 16 66 | -- check/create marker colors 67 | 68 | return true 69 | end 70 | 71 | 72 | local function findLayer(app, layername) 73 | local spr = app.activeSprite 74 | if not spr then 75 | app.alert("There is no active sprite!") 76 | return 77 | end 78 | 79 | for _, layer in ipairs(spr.layers) do 80 | if layer.name == layername then 81 | return layer 82 | end 83 | end 84 | 85 | return nil 86 | end 87 | 88 | 89 | 90 | ---------------------------------------------------------------------- 91 | -- MAIN ACTIONS 92 | ---------------------------------------------------------------------- 93 | 94 | 95 | -- TODO: in each error cell mark the least frequent color with a different marker color 96 | local function mapErrors(app, dlgdata, doMarks) 97 | if checkPrerequesities(app)~=true then 98 | return 99 | end 100 | 101 | if doMarks == nil then doMarks = true end 102 | 103 | local numErrorCells = 0 104 | 105 | app.transaction( 106 | function() 107 | 108 | local spr = app.activeSprite 109 | 110 | -- Store active layer (so we can restore later) 111 | actLayer = app.activeLayer 112 | 113 | -- Remove previous error layer if any 114 | local elayer = findLayer(app, "Errormap") 115 | 116 | if elayer ~= nil then 117 | spr:deleteLayer(elayer) 118 | end 119 | 120 | -- Get image from the active frame of the active sprite 121 | local img = Image(spr.spec) 122 | img:drawSprite(spr, app.activeFrame) 123 | 124 | -- Create empty error image 125 | local errimg 126 | if doMarks then 127 | errimg = Image(spr.spec) 128 | errimg:clear() 129 | end 130 | 131 | for cy = 0, 25 do 132 | for cx = 0, 40 do 133 | local cellCols = {} 134 | for ccy = 0, 7 do 135 | for ccx = 0, 7 do 136 | local col = img:getPixel(cx * 8 + ccx, cy * 8 + ccy) 137 | if not setContains(cellCols, col) then 138 | addToSet(cellCols, col) 139 | end 140 | end 141 | end 142 | 143 | if setCount(cellCols) > 2 then 144 | numErrorCells = numErrorCells+1 145 | if doMarks then 146 | for ccy = 0, 7 do 147 | for ccx = 0, 7 do 148 | errimg:putPixel(cx * 8 + ccx, cy * 8 + ccy, 17) 149 | end 150 | end 151 | end 152 | end 153 | end 154 | end 155 | 156 | -- add error layer to the image 157 | if doMarks then 158 | elayer = spr:newLayer() 159 | elayer.name = "Errormap" 160 | elayer.opacity = 128 161 | spr:newCel(elayer, app.activeFrame, errimg) 162 | elayer:cel(app.activeFrame).image = errimg 163 | end 164 | 165 | -- restore active layer to stored (adding the error layer selects that layer as active) 166 | if actLayer ~= nil then 167 | app.activeLayer = actLayer 168 | end 169 | 170 | app.refresh() 171 | end 172 | ) 173 | return numErrorCells 174 | end 175 | 176 | 177 | 178 | -- Check image for opportunities = cells which have less than the available colors 179 | local function mapOpportunities(app, dlgdata) 180 | if checkPrerequesities(app)~=true then 181 | return 182 | end 183 | 184 | app.transaction( 185 | function() 186 | 187 | -- Store active layer (so we can restore later) 188 | actLayer = app.activeLayer 189 | 190 | local spr = app.activeSprite 191 | 192 | -- Remove previous opportunity layer if any 193 | local opplayer = findLayer(app, "Oppmap") 194 | 195 | if opplayer ~= nil then 196 | spr:deleteLayer(opplayer) 197 | end 198 | 199 | -- Get image from the active frame of the active sprite 200 | local img = Image(spr.spec) 201 | img:drawSprite(spr, app.activeFrame) 202 | 203 | -- Create empty opportunity map image 204 | local oppimg = Image(spr.spec) 205 | oppimg:clear() 206 | 207 | for cy = 0, 25 do 208 | for cx = 0, 40 do 209 | local cellCols = {} 210 | for ccy = 0, 7 do 211 | for ccx = 0, 7 do 212 | local col = img:getPixel(cx * 8 + ccx, cy * 8 + ccy) 213 | if not setContains(cellCols, col) then 214 | addToSet(cellCols, col) 215 | end 216 | end 217 | end 218 | 219 | if setCount(cellCols) < 2 then 220 | for ccy = 0, 7 do 221 | for ccx = 0, 7 do 222 | oppimg:putPixel(cx * 8 + ccx, cy * 8 + ccy, 20) 223 | end 224 | end 225 | end 226 | end 227 | end 228 | 229 | -- add opportunity map layer to the image 230 | opplayer = spr:newLayer() 231 | opplayer.name = "Oppmap" 232 | opplayer.opacity = 128 233 | spr:newCel(opplayer, app.activeFrame, oppimg) 234 | opplayer:cel(app.activeFrame).image = oppimg 235 | 236 | -- restore active layer to stored (adding the error layer selects that layer as active) 237 | if actLayer ~= nil then 238 | app.activeLayer = actLayer 239 | end 240 | 241 | app.refresh() 242 | end 243 | ) 244 | end 245 | 246 | 247 | 248 | local function toggleLayer(app, dlgdata, layername) 249 | local elayer = findLayer(app, layername) 250 | if elayer ~= nil then 251 | elayer.isVisible = not elayer.isVisible 252 | app.refresh() 253 | end 254 | 255 | end 256 | 257 | 258 | 259 | 260 | 261 | ---------------------------------------------------------------------- 262 | -- START 263 | ---------------------------------------------------------------------- 264 | 265 | 266 | buildWindow = function(bounds) 267 | d = Dialog("Mark bad cells (C64 hires)") 268 | :button {id = "check", text = "&Check", focus = true, onclick = function() mapErrors(app, d.data, true) end} 269 | :button {id = "toggle", text = "Toggle Errormap", onclick = function() toggleLayer(app, d.data, "Errormap") end} 270 | :newrow() 271 | :button {id = "opp", text = "&OppCheck", onclick = function() mapOpportunities(app, d.data) end} 272 | :button {id = "opptoggle", text = "Toggle Oppmap", onclick = function() toggleLayer(app, d.data, "Oppmap") end} 273 | 274 | -- restore dialog position and size if we got data for it 275 | if bounds then 276 | -- Not really working... Aseprite bug? 277 | d.bounds = bounds 278 | -- If there is one more command here, the position will be restored, but width/height still resets 279 | d.bounds.x = d.bounds.x 280 | end 281 | d:show {wait = false} 282 | end 283 | 284 | buildWindow() 285 | -------------------------------------------------------------------------------- /C64 Multicolor Check.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | -- 3 | -- C64 multicolor format helpers 4 | -- 5 | ---------------------------------------------------------------------- 6 | 7 | 8 | local actLayer = nil 9 | local bgColor = Color(0) 10 | -- Forward declarations 11 | local d 12 | local buildWindow = function() 13 | end 14 | 15 | 16 | ---------------------------------------------------------------------- 17 | -- HELPER FUNCTIONS 18 | ---------------------------------------------------------------------- 19 | 20 | local function addToSet(set, key) 21 | if set[key] == nil then 22 | set[key] = 1 23 | else 24 | set[key] = set[key]+1 25 | end 26 | end 27 | 28 | local function removeFromSet(set, key) 29 | set[key] = nil 30 | end 31 | 32 | local function setContains(set, key) 33 | return set[key] ~= nil 34 | end 35 | 36 | local function setCount(set) 37 | local count = 0 38 | for i in pairs(set) do 39 | count = count + 1 40 | end 41 | return count 42 | end 43 | 44 | 45 | 46 | local function checkPrerequesities(app) 47 | local spr = app.activeSprite 48 | if not spr then 49 | app.alert("There is no active sprite!") 50 | return false 51 | end 52 | 53 | if spr.width~=160 or spr.height ~= 200 then 54 | app.alert("The sprite dimensions should be 160x200!\n(Suggestion: set the pixel aspect ratio to 2:1!)") 55 | return false 56 | end 57 | 58 | if spr.colorMode~= ColorMode.INDEXED then 59 | app.alert("The sprite should use indexed color mode!") 60 | return false 61 | end 62 | 63 | -- TODO: 64 | -- colors check: 65 | -- check actual colors against a built in palette? 66 | -- just check the number of colors > 16 67 | -- check/create marker colors 68 | 69 | return true 70 | end 71 | 72 | 73 | local function findLayer(app, layername) 74 | local spr = app.activeSprite 75 | if not spr then 76 | app.alert("There is no active sprite!") 77 | return 78 | end 79 | 80 | for _, layer in ipairs(spr.layers) do 81 | if layer.name == layername then 82 | return layer 83 | end 84 | end 85 | 86 | return nil 87 | end 88 | 89 | 90 | 91 | ---------------------------------------------------------------------- 92 | -- MAIN ACTIONS 93 | ---------------------------------------------------------------------- 94 | 95 | 96 | -- TODO: in each error cell mark the least frequent color with a different marker color 97 | local function mapErrors(app, dlgdata, bgCol, doMarks) 98 | if checkPrerequesities(app)~=true then 99 | return 100 | end 101 | 102 | bgCol = bgCol or dlgdata.bgCol.index 103 | if doMarks == nil then doMarks = true end 104 | 105 | local numErrorCells = 0 106 | 107 | app.transaction( 108 | function() 109 | 110 | local spr = app.activeSprite 111 | 112 | -- Store active layer (so we can restore later) 113 | actLayer = app.activeLayer 114 | 115 | -- Remove previous error layer if any 116 | local elayer = findLayer(app, "Errormap") 117 | 118 | if elayer ~= nil then 119 | spr:deleteLayer(elayer) 120 | end 121 | 122 | -- Get image from the active frame of the active sprite 123 | local img = Image(spr.spec) 124 | img:drawSprite(spr, app.activeFrame) 125 | 126 | -- Create empty error image 127 | local errimg 128 | if doMarks then 129 | errimg = Image(spr.spec) 130 | errimg:clear() 131 | end 132 | 133 | for cy = 0, 25 do 134 | for cx = 0, 40 do 135 | local cellCols = {} 136 | for ccy = 0, 7 do 137 | for ccx = 0, 3 do 138 | local col = img:getPixel(cx * 4 + ccx, cy * 8 + ccy) 139 | if not setContains(cellCols, col) then 140 | addToSet(cellCols, col) 141 | end 142 | end 143 | end 144 | 145 | if setCount(cellCols) > 4 or (setCount(cellCols) == 4 and not setContains(cellCols, bgCol)) then 146 | numErrorCells = numErrorCells+1 147 | if doMarks then 148 | for ccy = 0, 7 do 149 | for ccx = 0, 3 do 150 | errimg:putPixel(cx * 4 + ccx, cy * 8 + ccy, 17) 151 | end 152 | end 153 | end 154 | end 155 | end 156 | end 157 | 158 | -- add error layer to the image 159 | if doMarks then 160 | elayer = spr:newLayer() 161 | elayer.name = "Errormap" 162 | elayer.opacity = 128 163 | spr:newCel(elayer, app.activeFrame, errimg) 164 | elayer:cel(app.activeFrame).image = errimg 165 | end 166 | 167 | -- restore active layer to stored (adding the error layer selects that layer as active) 168 | if actLayer ~= nil then 169 | app.activeLayer = actLayer 170 | end 171 | 172 | app.refresh() 173 | end 174 | ) 175 | return numErrorCells 176 | end 177 | 178 | 179 | 180 | -- Check image for opportunities = cells which have less than the available colors 181 | local function mapOpportunities(app, dlgdata) 182 | if checkPrerequesities(app)~=true then 183 | return 184 | end 185 | 186 | app.transaction( 187 | function() 188 | 189 | -- Store active layer (so we can restore later) 190 | actLayer = app.activeLayer 191 | 192 | local spr = app.activeSprite 193 | 194 | -- Remove previous opportunity layer if any 195 | local opplayer = findLayer(app, "Oppmap") 196 | 197 | if opplayer ~= nil then 198 | spr:deleteLayer(opplayer) 199 | end 200 | 201 | -- Get image from the active frame of the active sprite 202 | local img = Image(spr.spec) 203 | img:drawSprite(spr, app.activeFrame) 204 | 205 | -- Create empty opportunity map image 206 | local oppimg = Image(spr.spec) 207 | oppimg:clear() 208 | 209 | for cy = 0, 25 do 210 | for cx = 0, 40 do 211 | local cellCols = {} 212 | for ccy = 0, 7 do 213 | for ccx = 0, 3 do 214 | local col = img:getPixel(cx * 4 + ccx, cy * 8 + ccy) 215 | if not setContains(cellCols, col) then 216 | addToSet(cellCols, col) 217 | end 218 | end 219 | end 220 | 221 | local cellCount = setCount(cellCols) 222 | if cellCount <= 3 or (cellCount == 3 and not setContains(cellCols, dlgdata.bgCol.index)) then 223 | local markCol = 20 224 | if cellCount == 2 then 225 | markCol = 19 226 | end 227 | if cellCount == 3 then 228 | markCol = 18 229 | end 230 | for ccy = 0, 7 do 231 | for ccx = 0, 3 do 232 | oppimg:putPixel(cx * 4 + ccx, cy * 8 + ccy, markCol) 233 | end 234 | end 235 | end 236 | end 237 | end 238 | 239 | -- add opportunity map layer to the image 240 | opplayer = spr:newLayer() 241 | opplayer.name = "Oppmap" 242 | opplayer.opacity = 128 243 | spr:newCel(opplayer, app.activeFrame, oppimg) 244 | opplayer:cel(app.activeFrame).image = oppimg 245 | 246 | -- restore active layer to stored (adding the error layer selects that layer as active) 247 | if actLayer ~= nil then 248 | app.activeLayer = actLayer 249 | end 250 | 251 | app.refresh() 252 | end 253 | ) 254 | end 255 | 256 | 257 | 258 | local function toggleLayer(app, dlgdata, layername) 259 | local elayer = findLayer(app, layername) 260 | if elayer ~= nil then 261 | elayer.isVisible = not elayer.isVisible 262 | app.refresh() 263 | end 264 | 265 | end 266 | 267 | 268 | 269 | --TODO: 270 | -- find best bg color 271 | -- find error cells 272 | -- find the color with the fewest pixels in that cell 273 | -- change it to...? the closest color from the other 3? What is the closest? In luma? 274 | local function quickFix(app, dlgdata) 275 | if checkPrerequesities(app)~=true then 276 | return 277 | end 278 | 279 | app.transaction( 280 | function() 281 | local spr = app.activeSprite 282 | 283 | local img = Image(spr.spec) 284 | img:drawSprite(spr, app.activeFrame) 285 | 286 | local fixlayer = spr:newLayer() 287 | fixlayer.name = "Fixlayer" 288 | spr:newCel(fixlayer, app.activeFrame, img) 289 | fixlayer:cel(app.activeFrame).image = img 290 | end 291 | ) 292 | end 293 | 294 | 295 | 296 | local function findBGColor(app, dlgdata) 297 | if checkPrerequesities(app)~=true then 298 | return 299 | end 300 | 301 | local spr = app.activeSprite 302 | 303 | local errlayer = findLayer(app, "Errormap") 304 | if errlayer ~= nil then 305 | spr:deleteLayer(errlayer) 306 | end 307 | local opplayer = findLayer(app, "Oppmap") 308 | if opplayer ~= nil then 309 | spr:deleteLayer(opplayer) 310 | end 311 | 312 | local minErrors = 40*25+1 313 | local minIdx = nil 314 | for i = 0,16 do 315 | local numErrors = mapErrors(app, dlgdata, i, false) 316 | if numErrors 1:colorRam, 2:screenRam first half, 3:screenRam second half 81 | local colors = {} 82 | 83 | for cy = 0, 24 do 84 | for cx = 0, 39 do 85 | local cellCols = {} 86 | cellCols[0] = data.bgCol.index 87 | 88 | for ccy = 0, 7 do 89 | local row = "" 90 | for ccx = 0, 3 do 91 | local col = img:getPixel(cx * 4 + ccx, cy * 8 + ccy) 92 | 93 | local idx = nil 94 | local last = 0 95 | for i = 0, 3 do 96 | if cellCols[i] ~= nil then 97 | last = i 98 | if cellCols[i] == col then 99 | idx = i 100 | end 101 | end 102 | end 103 | 104 | if idx == nil then 105 | cellCols[last + 1] = col 106 | idx = last + 1 107 | end 108 | 109 | if idx == 0 then 110 | row = row .. "00" 111 | elseif idx == 1 then 112 | row = row .. "11" 113 | elseif idx == 2 then 114 | row = row .. "10" 115 | elseif idx == 3 then 116 | row = row .. "01" 117 | end 118 | end 119 | 120 | table.insert(bitmap, tonumber(row, 2)) 121 | end 122 | 123 | -- pad with 0 color indeces if there are less than 4 colors in a cell 124 | for i = 0, 3 do 125 | if cellCols[i] == nil then 126 | cellCols[i] = 0 127 | end 128 | end 129 | -- remove the background color from the cell colors table 130 | cellCols[0] = nil 131 | -- store cell colors in the temp colors table 132 | table.insert(colors, cellCols) 133 | 134 | end 135 | end 136 | 137 | -- TODO: Check if everything is ok - #bitmap should be 8000, #colors should be 1000 138 | -- is that enough? 139 | -- print(#bitmap, #colors) 140 | 141 | -- Build export structures 142 | local colorRAM = {} 143 | local screenRAM = {} 144 | 145 | for i = 1, 1000 do 146 | -- the first value from colors goes to the color ram 147 | colorRAM[i] = colors[i][1] 148 | -- the sceond and third forms a byte, and goes to the screen ram 149 | screenRAM[i] = colors[i][2] + colors[i][3] * 16 150 | end 151 | 152 | local outfname = data.fname 153 | if data.prg == true then 154 | outfname = outfname .. ".prg" 155 | else 156 | outfname = outfname .. ".kla" 157 | end 158 | 159 | local out = io.open(outfname, "wb") 160 | 161 | -- if the format is prg, read the viewer and append the data to that 162 | if data.prg == true then 163 | local inprg = io.open(script_path() .. "koalaview.prg", "rb") 164 | local viewerbin = inprg:read("*all") 165 | out:write(viewerbin) 166 | end 167 | 168 | -- Write load address 169 | if data.prg == true then 170 | -- ignore the dialog setting, the viewer expects the koala at 0x6000 171 | out:write(string.char(00, 0x60)) 172 | else 173 | -- Use the address from the dialog 174 | local secondhalf = tonumber(string.sub(data.loadaddress, 3,4),16) 175 | local firsthalf = tonumber(string.sub(data.loadaddress, 1,2),16) 176 | out:write(string.char(secondhalf, firsthalf)) 177 | end 178 | out:write(string.char(table.unpack(bitmap))) 179 | out:write(string.char(table.unpack(screenRAM))) 180 | out:write(string.char(table.unpack(colorRAM))) 181 | out:write(string.char(data.bgCol.index)) 182 | out:close() 183 | 184 | -- FIXME: Not working for some reason... 185 | -- the os command is ok (checked in the terminal), aseprite asks for permission to run it, but nothing happens afterwards 186 | -- aseprite bug? or some kind of os level permission thing? 187 | 188 | -- if data.prg == true then 189 | -- local crunchedfname = string.gsub(outfname, "[.]", ".c.") 190 | -- local term, status, num = os.execute("exomizer sfx sys "..outfname.." -o "..crunchedfname) 191 | -- end 192 | -------------------------------------------------------------------------------- /Import Koala.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | -- 3 | -- Import C64 Koala format image 4 | -- 5 | ---------------------------------------------------------------------- 6 | 7 | -- Build import dialog 8 | local d = 9 | Dialog("Import C64 Koala format") 10 | :label {text = "Please enter a file name below:"} 11 | :entry {id = "fname", text = "", focus = true} 12 | :button {id = "ok", text = "&OK"} 13 | :button {text = "&Cancel"} 14 | :show() 15 | 16 | local data = d.data 17 | if not data.ok then 18 | return 19 | end 20 | 21 | if data.fname == "" then 22 | app.alert {title="Error", text="No filename was given."} 23 | return 24 | end 25 | 26 | --fname = "/Users/viza/VizaDocs/C64/gfx/sentinels.kla" 27 | local infile = io.open(data.fname, "rb") 28 | if infile == nil then 29 | app.alert {title="Error", text="Can't open file " .. data.fname .. " for import."} 30 | return 31 | end 32 | 33 | local kla = infile:read("*all") 34 | infile:close() 35 | 36 | if #kla ~= 10003 then 37 | app.alert {title="Error", text="The kla file should be exactly 10003 bytes in size, the opened file is " .. #kla} 38 | return 39 | end 40 | 41 | -- Create a new sprite to load into 42 | local newSpr = Sprite(160, 200, ColorMode.INDEXED) 43 | 44 | -- Set up C64 palette (using the colors from the built-in C64 palette) 45 | local pal = newSpr.palettes[1] 46 | pal:resize(16) 47 | pal:setColor(00, Color {r = 0, g = 0, b = 0}) 48 | pal:setColor(01, Color {r = 255, g = 255, b = 255}) 49 | pal:setColor(02, Color {r = 136, g = 57, b = 50}) 50 | pal:setColor(03, Color {r = 103, g = 182, b = 189}) 51 | pal:setColor(04, Color {r = 139, g = 63, b = 150}) 52 | pal:setColor(05, Color {r = 85, g = 160, b = 73}) 53 | pal:setColor(06, Color {r = 64, g = 49, b = 141}) 54 | pal:setColor(07, Color {r = 191, g = 206, b = 114}) 55 | pal:setColor(08, Color {r = 139, g = 84, b = 41}) 56 | pal:setColor(09, Color {r = 87, g = 66, b = 0}) 57 | pal:setColor(10, Color {r = 184, g = 105, b = 98}) 58 | pal:setColor(11, Color {r = 80, g = 80, b = 80}) 59 | pal:setColor(12, Color {r = 120, g = 120, b = 120}) 60 | pal:setColor(13, Color {r = 148, g = 224, b = 137}) 61 | pal:setColor(14, Color {r = 120, g = 105, b = 196}) 62 | pal:setColor(15, Color {r = 159, g = 159, b = 159}) 63 | 64 | -- TODO: Set pixel aspect to double width 65 | -- Unfortunately it seems that it is not possible to set up this from script right now :( 66 | 67 | app.command.BackgroundFromLayer() 68 | local img = newSpr.cels[1].image 69 | 70 | local bgcol = string.byte(kla, 10003) 71 | 72 | local function getColorFor(cx, cy, idx) 73 | local colbyteidx = 0 74 | 75 | if idx == 1 then 76 | -- screen ram bits 4-7 77 | colbyteidx = 8003 + cx + cy * 40 78 | return ((string.byte(kla, colbyteidx) & 0xF0) >> 4) 79 | end 80 | 81 | if idx == 2 then 82 | -- screen ram bits 0-3 83 | colbyteidx = 8003 + cx + cy * 40 84 | return (string.byte(kla, colbyteidx) & 0x0F) 85 | end 86 | 87 | if idx == 3 then 88 | -- color ram bits 4-7 89 | colbyteidx = 9003 + cx + cy * 40 90 | return (string.byte(kla, colbyteidx) & 0x0F) 91 | end 92 | 93 | -- background color 94 | return bgcol 95 | end 96 | 97 | for cy = 0, 24 do 98 | for cx = 0, 39 do 99 | 100 | for ccy = 0, 7 do 101 | -- get byte from bitmap data for current attribute cell's current row 102 | local bmpbyte = string.byte(kla, 3 + cy * 8 * 40 + cx * 8 + ccy) 103 | 104 | -- extract the four color indexes from the byte 105 | local x = cx * 4 106 | local y = cy * 8 + ccy 107 | -- first two bits 108 | img:putPixel(x , y, getColorFor(cx, cy, (bmpbyte & 192) >> 6)) 109 | -- second two bits 110 | img:putPixel(x+1, y, getColorFor(cx, cy, (bmpbyte & 48) >> 4)) 111 | -- third two bits 112 | img:putPixel(x+2, y, getColorFor(cx, cy, (bmpbyte & 12) >> 2)) 113 | -- last two bits 114 | img:putPixel(x+3, y, getColorFor(cx, cy, (bmpbyte & 3))) 115 | end 116 | 117 | end 118 | end 119 | 120 | app.refresh() 121 | -------------------------------------------------------------------------------- /exporthires_sshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viza74/c64helperscriptsforaseprite/747854de3bb0b2b8754f63a1f2e181a4a3ab5a0f/exporthires_sshot.png -------------------------------------------------------------------------------- /exportkoala_sshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viza74/c64helperscriptsforaseprite/747854de3bb0b2b8754f63a1f2e181a4a3ab5a0f/exportkoala_sshot.png -------------------------------------------------------------------------------- /hirescheck_sshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viza74/c64helperscriptsforaseprite/747854de3bb0b2b8754f63a1f2e181a4a3ab5a0f/hirescheck_sshot.png -------------------------------------------------------------------------------- /hiresstarter.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viza74/c64helperscriptsforaseprite/747854de3bb0b2b8754f63a1f2e181a4a3ab5a0f/hiresstarter.aseprite -------------------------------------------------------------------------------- /hiresview.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viza74/c64helperscriptsforaseprite/747854de3bb0b2b8754f63a1f2e181a4a3ab5a0f/hiresview.prg -------------------------------------------------------------------------------- /importkoala_sshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viza74/c64helperscriptsforaseprite/747854de3bb0b2b8754f63a1f2e181a4a3ab5a0f/importkoala_sshot.png -------------------------------------------------------------------------------- /koalaview.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viza74/c64helperscriptsforaseprite/747854de3bb0b2b8754f63a1f2e181a4a3ab5a0f/koalaview.prg -------------------------------------------------------------------------------- /multicheck_sshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viza74/c64helperscriptsforaseprite/747854de3bb0b2b8754f63a1f2e181a4a3ab5a0f/multicheck_sshot.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # C64 image format helper scripts for [Aseprite](https://www.aseprite.org/) 2 | 3 | 4 | ## Installing 5 | 6 | >You need to run at least Aseprite v1.2.10 (beta at the time of writing) with lua scripting support. 7 | 8 | Click on `File/Scripts/Open script folder` and copy the files from this repo there (well actually the lua and prg files are enough). Restart Aseprite, and the new commands should show up in the `File/Scripts` menu. 9 | 10 | > Tip: You can assign keyboard shortcuts to scripts in the `Edit/Keyboard shorcuts` menu 11 | 12 | ## Getting started 13 | The multicolor bitmap mode on the C64 is 160x200 (with double wide pixels) resolution image, where each "attribute cell" is 4x8 pixels, and can contain maximum of four colors: 3 can be unique in every cell, and one common color (the "background color"). 14 | 15 | The simplest way to start is to duplicate the included aseprite image, and use that, it is set up the way as the scripts expect it. 16 | 17 | But if you want to do it manually: 18 | 19 | In the "New Sprite" dialog, set: 20 | * Resolution: 160x200 21 | * Color mode: Indexed 22 | * Open the `Advanced options` section, set the `Pixel aspect ratio` to `Double-wide Pixels 2:1` 23 | * Palette: `Presets` (above the palette), and load the C64 palette from there. 24 | * The scripts work with a couple of extra colors for the markings outside of the 16 color C64 palette. Add the following colors: 25 | * index 16 (purple) - not actually used by the scripts, I use it as a transparent color. 26 | * index 17 (bright red) - this will mark cells with errors 27 | * index 18 (orange) - cells with one more possible color 28 | * index 19 (blue) - cells with two more possible colors 29 | * index 20 (green) - cells with three more possible colors 30 | 31 | >Tip: set the grid dimension to 4x8 in the `View/Grid/Grid settings` menu. 32 | 33 | 34 | 35 | ## C64 multicolor check 36 | 37 | ![](multicheck_sshot.png) 38 | 39 | ### Find best BG color 40 | Brute force search for the color which produces the least errors in the image. 41 | ### Color field 42 | The currently used background color. The previous option will find the best one, but can be set manually. 43 | ### Check 44 | Check the image for errors. 45 | It will create a new layer called "Error map", and fills every attribute cells with the color index 18 (red if you use the included starter image) which contains more than 3 colors + bg color. 46 | ### Toggle error map 47 | Handy shortcut to toggle the visibility of the error map. 48 | ### OppCheck 49 | Checks the image for "opportunities" - attribute cells with less colors then allowed - i.e. Where can I put more colors? 50 | It will create a new layer called "Oppmap" and color codes the cells - marks cells with one color with index 20 (green), cells with two colors with index 19 (blue), and cells with 3 colors and no background color with index 18 (orange). 51 | ### Toggle Oppmap 52 | Toggles the visibility of the opportunity map on and off. 53 | 54 | 55 | 56 | ## Export Koala 57 | ![](exportkoala_sshot.png) 58 | 59 | ### Save as 60 | The filename to save the koala/prg file, without extension. By default it is the same as the currently open asperite file name, but you can modify it by hand. 61 | Unfortunately it seems that there are no file picker accessible from the scripts yet. :( 62 | ### Load at $6000 63 | Specify the load address of the koala file. 64 | ### BG Color 65 | Which color index to use as the background color? 66 | ### Koala/PRG radio buttons 67 | Choose the file format: 68 | * Koala will export the image as pure data .kla file, starting at the memory address 0x6000 69 | * PRG will use the file "koalaview.prg" in the scripts directory, and appends the koala binary to that. The program is runnable on a C64, and simply displays the appended koala image. 70 | 71 | 72 | ## C64 Hires check 73 | 74 | ![](hirescheck_sshot.png) 75 | 76 | ### Check 77 | Check the image for errors. 78 | It will create a new layer called "Error map", and fills every attribute cells with the color index 18 (red if you use the included starter image) which contains more than 2 colors. 79 | ### Toggle error map 80 | Handy shortcut to toggle the visibility of the error map. 81 | ### OppCheck 82 | Checks the image for "opportunities" - attribute cells with less colors then allowed - i.e. Where can I put more colors? 83 | It will create a new layer called "Oppmap" and color codes the cells - marks cells with only one color with index 20 (green). 84 | ### Toggle Oppmap 85 | Toggles the visibility of the opportunity map on and off. 86 | 87 | 88 | 89 | ## Export Hires 90 | ![](exporthires_sshot.png) 91 | 92 | ### Save as 93 | The filename to save the hed/prg file, without extension. By default it is the same as the currently open asperite file name, but you can modify it by hand. 94 | Unfortunately it seems that there are no file picker accessible from the scripts yet. :( 95 | ### Load at $2000 96 | Specify the load address of the Hi-Eddi file. 97 | ### hed/PRG radio buttons 98 | Choose the file format: 99 | * hed will export the image as pure data .hed file. 100 | * PRG will use the file "hiresview.prg" in the scripts directory, and appends the hed binary to that. The program is runnable on a C64, and simply displays the appended image. 101 | 102 | ## Import Koala 103 | ![](importkoala_sshot.png) 104 | 105 | Just enter/paste a filename (with full path) in the entry box to load a C64 Koala format file into Aseprite. 106 | 107 | Unfortunately due to the limitations currently in Aseprite's script API, I can't provide a file selection dialog. :( 108 | Another missing function is the ability to set double wide pixel aspect ratio from script, so you also have to do that manually in the `Sprite/Properties...` menu. -------------------------------------------------------------------------------- /starter.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Viza74/c64helperscriptsforaseprite/747854de3bb0b2b8754f63a1f2e181a4a3ab5a0f/starter.aseprite --------------------------------------------------------------------------------