@@ -48,9 +48,14 @@ export type PixelEditorImageFormat = {
4848 il ?: boolean // interleave images row by row
4949 aspect ?: number // aspect ratio
5050 xform ?: string // CSS transform
51+ art ?: number // artifact color, Apple II
5152 destfmt ?: PixelEditorImageFormat
5253} ;
5354
55+ function getArtBit ( fmt : PixelEditorImageFormat ) : number | null {
56+ return fmt . art ? ( fmt . brev ? 0 : 7 ) : null ;
57+ }
58+
5459export type PixelEditorPaletteFormat = {
5560 pal ?: number | string
5661 n ?: number
@@ -836,6 +841,7 @@ export class ImageChooser {
836841 rgbimgs : Uint32Array [ ] ;
837842 width : number ;
838843 height : number ;
844+ gapX : number | null = null ;
839845 viewers : Viewer [ ] ;
840846
841847 recreate ( parentdiv : JQuery , onclick ) {
@@ -849,8 +855,9 @@ export class ImageChooser {
849855 var viewer = new Viewer ( ) ;
850856 viewer . width = this . width ;
851857 viewer . height = this . height ;
858+ viewer . gapX = this . gapX ;
852859 viewer . recreate ( ) ;
853- viewer . canvas . style . width = ( viewer . width * cscale ) + 'px' ; // TODO
860+ viewer . canvas . style . width = ( viewer . displayWidth * cscale ) + 'px' ; // TODO
854861 viewer . canvas . title = '$' + hex ( i ) ;
855862 viewer . updateImage ( imdata ) ;
856863 $ ( viewer . canvas ) . addClass ( 'asset_cell' ) ;
@@ -909,6 +916,7 @@ export class CharmapEditor extends PixNode {
909916 this . rgbimgs = this . left . rgbimgs ;
910917 // if chooser already exists with same number of images, update in place
911918 if ( this . chooser && this . chooser . viewers && this . chooser . viewers . length == this . rgbimgs . length ) {
919+ this . updateArtInfo ( ) ;
912920 this . chooser . updateImages ( this . rgbimgs ) ;
913921 // keep rgbimgs pointing to viewer buffers so edits propagate back via refreshLeft
914922 for ( var i = 0 ; i < this . chooser . viewers . length ; i ++ ) {
@@ -924,17 +932,27 @@ export class CharmapEditor extends PixNode {
924932 chooser . rgbimgs = this . rgbimgs ;
925933 chooser . width = this . fmt . w || 1 ;
926934 chooser . height = this . fmt . h || 1 ;
935+ var artBit = getArtBit ( this . fmt ) ;
936+ if ( artBit != null ) {
937+ chooser . gapX = this . fmt . brev ? artBit + 1 : artBit ;
938+ }
927939 chooser . recreate ( agrid , ( index , viewer ) => {
928940 // TODO: variable scale?
929941 const aspect = this . fmt . aspect || 1.0 ;
930942 const yscale = Math . min ( MAX_SCALE ,
931- MAX_SIZE_X / this . fmt . w * aspect ,
943+ MAX_SIZE_X / viewer . displayWidth * aspect ,
932944 MAX_SIZE_Y / this . fmt . h ) ;
933945 const xscale = yscale * aspect ;
934946 this . createEditor ( aeditor , viewer , xscale , yscale ) ;
935947 this . context . setCurrentEditor ( aeditor , $ ( viewer . canvas ) , this ) ;
936948 this . rgbimgs [ index ] = viewer . rgbdata ;
937949 } ) ;
950+ this . updateArtInfo ( ) ;
951+ if ( this . fmt . art ) {
952+ for ( var i = 0 ; i < this . chooser . viewers . length ; i ++ ) {
953+ this . chooser . viewers [ i ] . updateImage ( ) ;
954+ }
955+ }
938956 // add palette selector
939957 // TODO: only view when editing?
940958 var palizer = this . left ;
@@ -955,11 +973,27 @@ export class CharmapEditor extends PixNode {
955973 return true ;
956974 }
957975
976+ updateArtInfo ( ) {
977+ if ( ! this . fmt . art || ! this . chooser || ! this . chooser . viewers ) return ;
978+ var bitsperword = this . fmt . bpw || 8 ;
979+ var artBit = getArtBit ( this . fmt ) ! ;
980+ var indexedImages = this . left . images ;
981+ if ( ! indexedImages ) return ;
982+ for ( var i = 0 ; i < this . chooser . viewers . length ; i ++ ) {
983+ var viewer = this . chooser . viewers [ i ] ;
984+ viewer . artInfo = {
985+ artBit : artBit ,
986+ bitsperword : bitsperword ,
987+ indexedImage : indexedImages [ i ]
988+ } ;
989+ }
990+ }
991+
958992 createEditor ( aeditor : JQuery , viewer : Viewer , xscale : number , yscale : number ) : PixEditor {
959993 var im = new PixEditor ( ) ;
960994 im . createWith ( viewer ) ;
961995 im . updateImage ( ) ;
962- var w = viewer . width * xscale ;
996+ var w = im . displayWidth * xscale ;
963997 var h = viewer . height * yscale ;
964998 im . canvas . style . width = w + 'px' ; // TODO
965999 im . canvas . style . height = h + 'px' ; // TODO
@@ -1006,32 +1040,50 @@ export class Viewer {
10061040
10071041 width : number ;
10081042 height : number ;
1043+ gapX : number | null = null ;
10091044 canvas : HTMLCanvasElement ;
10101045 ctx : CanvasRenderingContext2D ;
10111046 imagedata : ImageData ;
10121047 rgbdata : Uint32Array ;
1048+ displayImageData : ImageData ;
1049+ displayRgbData : Uint32Array ;
10131050 peerviewers : Viewer [ ] ;
1051+ artInfo : { artBit : number , bitsperword : number , indexedImage : Uint8Array } | null = null ;
1052+
1053+ get displayWidth ( ) : number {
1054+ return this . width + ( this . gapX != null ? 1 : 0 ) ;
1055+ }
10141056
10151057 recreate ( ) {
10161058 this . canvas = this . newCanvas ( ) ;
10171059 this . imagedata = this . ctx . createImageData ( this . width , this . height ) ;
10181060 this . rgbdata = new Uint32Array ( this . imagedata . data . buffer ) ;
1061+ if ( this . gapX != null ) {
1062+ this . displayImageData = this . ctx . createImageData ( this . displayWidth , this . height ) ;
1063+ this . displayRgbData = new Uint32Array ( this . displayImageData . data . buffer ) ;
1064+ }
10191065 this . peerviewers = [ this ] ;
10201066 }
10211067
10221068 createWith ( pv : Viewer ) {
10231069 this . width = pv . width ;
10241070 this . height = pv . height ;
1071+ this . gapX = pv . gapX ;
10251072 this . imagedata = pv . imagedata ;
10261073 this . rgbdata = pv . rgbdata ;
10271074 this . canvas = this . newCanvas ( ) ;
1075+ if ( this . gapX != null ) {
1076+ this . displayImageData = this . ctx . createImageData ( this . displayWidth , this . height ) ;
1077+ this . displayRgbData = new Uint32Array ( this . displayImageData . data . buffer ) ;
1078+ }
10281079 pv . peerviewers . push ( this ) ;
10291080 this . peerviewers = pv . peerviewers ;
1081+ this . artInfo = pv . artInfo ;
10301082 }
10311083
10321084 newCanvas ( ) : HTMLCanvasElement {
10331085 var c = document . createElement ( 'canvas' ) ;
1034- c . width = this . width ;
1086+ c . width = this . displayWidth ;
10351087 c . height = this . height ;
10361088 //if (fmt.xform) c.style.transform = fmt.xform;
10371089 c . classList . add ( "pixels" ) ;
@@ -1045,7 +1097,24 @@ export class Viewer {
10451097 this . rgbdata . set ( imdata ) ;
10461098 }
10471099 for ( let v of this . peerviewers ) {
1048- v . ctx . putImageData ( this . imagedata , 0 , 0 ) ;
1100+ if ( v . gapX != null && v . displayRgbData ) {
1101+ var gx = v . gapX ;
1102+ var dw = v . displayWidth ;
1103+ for ( var y = 0 ; y < this . height ; y ++ ) {
1104+ var srcOfs = y * this . width ;
1105+ var dstOfs = y * dw ;
1106+ for ( var x = 0 ; x < gx ; x ++ ) {
1107+ v . displayRgbData [ dstOfs + x ] = this . rgbdata [ srcOfs + x ] ;
1108+ }
1109+ v . displayRgbData [ dstOfs + gx ] = 0x00000000 ; // Transparent gap column.
1110+ for ( var x = gx ; x < this . width ; x ++ ) {
1111+ v . displayRgbData [ dstOfs + x + 1 ] = this . rgbdata [ srcOfs + x ] ;
1112+ }
1113+ }
1114+ v . ctx . putImageData ( v . displayImageData , 0 , 0 ) ;
1115+ } else {
1116+ v . ctx . putImageData ( this . imagedata , 0 , 0 ) ;
1117+ }
10491118 }
10501119 }
10511120}
@@ -1059,9 +1128,11 @@ class PixEditor extends Viewer {
10591128 palbtns : JQuery [ ] ;
10601129 offscreen : Map < string , number > = new Map ( ) ;
10611130
1062- getPositionFromEvent ( e ) {
1063- var x = Math . floor ( e . offsetX * this . width / $ ( this . canvas ) . width ( ) ) ;
1131+ getPositionFromEvent ( e ) : { x : number , y : number } {
1132+ var dw = this . displayWidth ;
1133+ var x = Math . floor ( e . offsetX * dw / $ ( this . canvas ) . width ( ) ) ;
10641134 var y = Math . floor ( e . offsetY * this . height / $ ( this . canvas ) . height ( ) ) ;
1135+ if ( this . gapX != null && x > this . gapX ) x -- ;
10651136 return { x : x , y : y } ;
10661137 }
10671138
@@ -1084,8 +1155,20 @@ class PixEditor extends Viewer {
10841155 var dragging = false ;
10851156
10861157 var pxls = $ ( this . canvas ) ;
1158+ var artDragIdx = - 1 ;
10871159 pxls . mousedown ( ( e ) => {
10881160 var pos = this . getPositionFromEvent ( e ) ;
1161+ if ( this . isArtPixel ( pos . x ) ) {
1162+ artDragIdx = this . toggleArtPixel ( pos . x , pos . y ) ;
1163+ dragging = true ;
1164+ $ ( document ) . mouseup ( ( e ) => {
1165+ $ ( document ) . off ( 'mouseup' ) ;
1166+ dragging = false ;
1167+ artDragIdx = - 1 ;
1168+ this . commit ( ) ;
1169+ } ) ;
1170+ return ;
1171+ }
10891172 dragcol = this . getPixel ( pos . x , pos . y ) == this . currgba ? this . palette [ 0 ] : this . currgba ;
10901173 this . setPixel ( pos . x , pos . y , this . currgba ) ;
10911174 dragging = true ;
@@ -1099,7 +1182,10 @@ class PixEditor extends Viewer {
10991182 } )
11001183 . mousemove ( ( e ) => {
11011184 var pos = this . getPositionFromEvent ( e ) ;
1102- if ( dragging ) {
1185+ if ( ! dragging ) return ;
1186+ if ( artDragIdx >= 0 && this . isArtPixel ( pos . x ) ) {
1187+ this . setArtPixel ( pos . x , pos . y , artDragIdx ) ;
1188+ } else if ( artDragIdx < 0 && ! this . isArtPixel ( pos . x ) ) {
11031189 this . setPixel ( pos . x , pos . y , dragcol ) ;
11041190 }
11051191 } ) ;
@@ -1111,6 +1197,31 @@ class PixEditor extends Viewer {
11111197 this . setPaletteColor ( 1 ) ;
11121198 }
11131199
1200+ isArtPixel ( x : number ) : boolean {
1201+ if ( ! this . artInfo ) return false ;
1202+ return ( x % this . artInfo . bitsperword ) == this . artInfo . artBit ;
1203+ }
1204+
1205+ toggleArtPixel ( x : number , y : number ) : number {
1206+ var info = this . artInfo ;
1207+ if ( ! info ) return - 1 ;
1208+ var ofs = y * this . width + x ;
1209+ var newIdx = info . indexedImage [ ofs ] ? 0 : 1 ;
1210+ this . setArtPixel ( x , y , newIdx ) ;
1211+ return newIdx ;
1212+ }
1213+
1214+ setArtPixel ( x : number , y : number , idx : number ) : void {
1215+ var info = this . artInfo ;
1216+ if ( ! info ) return ;
1217+ if ( x < 0 || x >= this . width || y < 0 || y >= this . height ) return ;
1218+ var ofs = y * this . width + x ;
1219+ if ( info . indexedImage [ ofs ] == idx ) return ;
1220+ info . indexedImage [ ofs ] = idx ;
1221+ this . rgbdata [ ofs ] = this . palette [ idx ] ;
1222+ this . updateImage ( ) ;
1223+ }
1224+
11141225 getPixel ( x : number , y : number ) : number {
11151226 x = Math . round ( x ) ;
11161227 y = Math . round ( y ) ;
@@ -1175,16 +1286,47 @@ class PixEditor extends Viewer {
11751286 }
11761287 }
11771288
1289+ copyNonArtPixels ( dst : Uint32Array , dstWidth : number , src : Uint32Array , srcWidth : number ) {
1290+ for ( var y = 0 ; y < this . height ; y ++ ) {
1291+ var si = 0 , di = 0 ;
1292+ for ( var x = 0 ; x < this . width ; x ++ ) {
1293+ var isArt = this . isArtPixel ( x ) ;
1294+ if ( ! isArt )
1295+ dst [ y * dstWidth + di ] = src [ y * srcWidth + si ] ;
1296+ if ( ! isArt || srcWidth === this . width ) si ++ ;
1297+ if ( ! isArt || dstWidth === this . width ) di ++ ;
1298+ }
1299+ }
1300+ }
1301+
11781302 remapPixels ( mapfn : ( x : number , y : number ) => number ) {
1303+ // Strip art bit columns so transforms ignore them.
1304+ var savedRgb = this . rgbdata ;
1305+ var savedWidth = this . width ;
1306+ if ( this . artInfo ) {
1307+ var bpw = this . artInfo . bitsperword ;
1308+ var strippedWidth = this . width - Math . floor ( this . width / bpw ) ;
1309+ var stripped = new Uint32Array ( strippedWidth * this . height ) ;
1310+ this . copyNonArtPixels ( stripped , strippedWidth , this . rgbdata , this . width ) ;
1311+ this . rgbdata = stripped ;
1312+ this . width = strippedWidth ;
1313+ }
1314+ // Apply the transform.
11791315 var i = 0 ;
11801316 var pixels = new Uint32Array ( this . rgbdata . length ) ;
11811317 for ( var y = 0 ; y < this . height ; y ++ ) {
11821318 for ( var x = 0 ; x < this . width ; x ++ ) {
1183- pixels [ i ] = mapfn ( x , y ) ;
1184- i ++ ;
1319+ pixels [ i ++ ] = mapfn ( x , y ) ;
11851320 }
11861321 }
1187- this . rgbdata . set ( pixels ) ;
1322+ // Restore art bit columns from the original data.
1323+ this . rgbdata = savedRgb ;
1324+ this . width = savedWidth ;
1325+ if ( this . artInfo ) {
1326+ this . copyNonArtPixels ( this . rgbdata , this . width , pixels , strippedWidth ) ;
1327+ } else {
1328+ this . rgbdata . set ( pixels ) ;
1329+ }
11881330 this . commit ( ) ;
11891331 }
11901332
0 commit comments