diff --git a/FuncDataV2.inc b/FuncDataV2.inc index 736be64..51f9c68 100644 --- a/FuncDataV2.inc +++ b/FuncDataV2.inc @@ -162,16 +162,3 @@ EorGfx .byte %10100010 ; constant .byte %10111110 ; constant .byte %10000000 ; constant - -FirstIdxTbl ; for 25 pixel - ds 7, 0 - .byte $fe - ds 7, 0 - .byte $01 - ds 7, 0 - IF QR_LEVEL = 0 || QR_LEVEL = 1 - .byte $bf ; 1st format bit is 1 - ELSE - .byte $3f ; 1st format bit is 0 - ENDIF - diff --git a/FuncDataV2S.inc b/FuncDataV2S.inc index 175876b..cbad965 100644 --- a/FuncDataV2S.inc +++ b/FuncDataV2S.inc @@ -171,15 +171,3 @@ GRP0RBlack IF QR_LEVEL = 1 EOR_GFX ~%10101000, ~%00100100 ENDIF - -FirstIdxTbl ; for 25 pixel - ds 7, 0 - .byte $fe - ds 7, 0 - .byte $01 - ds 7, 0 - IF QR_LEVEL = 0 || QR_LEVEL = 1 - .byte $bf ; 1st format bit is 1 - ELSE - .byte $3f ; 1st format bit is 0 - ENDIF diff --git a/QRCodeGen.inc b/QRCodeGen.inc index 2fddd37..4709f93 100644 --- a/QRCodeGen.inc +++ b/QRCodeGen.inc @@ -2,8 +2,20 @@ ; Q R C O D E C O N S T A N T S ;=============================================================================== +; Do NOT change these constants! + + IFNCONST QR_OVERLAP +QR_OVERLAP = 0 + ENDIF + IFNCONST QR_SINGLE_MASK +QR_SINGLE_MASK = 0 + ENDIF + IFNCONST QR_DIRECT_DRAW +QR_DIRECT_DRAW = 0 + ENDIF + IF QR_VERSION = 1 ;{ -QR_SIZE = 21 ; 21x21 QR code +; 21x21 QR code IF QR_LEVEL = 0 QR_DEGREE = 7 ; for version 1, level L QR codes ENDIF @@ -19,7 +31,7 @@ QR_DEGREE = 17 ; for version 1, level H QR codes ENDIF ;} IF QR_VERSION = 2 -QR_SIZE = 25 ; 25x25 QR code +; 25x25 QR code IF QR_LEVEL = 0 QR_DEGREE = 10 ; for version 2, level L QR codes ENDIF @@ -35,7 +47,7 @@ QR_DEGREE = 28 ; for version 2, level H QR codes ENDIF IF QR_VERSION = 3 ;{ -QR_SIZE = 29 ; 29x29 QR code +; 29x29 QR code IF QR_LEVEL = 0 QR_DEGREE = 15 ; for version 3, level L QR codes ENDIF @@ -49,6 +61,8 @@ QR_DEGREE = 26 ; for version 3, level M QR codes ENDIF ENDIF ;} +QR_SIZE = 17 + QR_VERSION * 4 + ; Calculate capacity based on version _QR_VAL SET (QR_VERSION * 16 + 128) * QR_VERSION + 64 IF QR_VERSION >= 2 @@ -219,24 +233,150 @@ RSMult SUBROUTINE bpl .loopI ENDM + IF QR_DIRECT_DRAW = 0 +;----------------------------------------------------------- + MAC _BLACK_FUNC +;----------------------------------------------------------- +; Blacks all function/alignment and timing pattern areas + +; assume bitmap is clear, only draw the pattern + ldy #4+2 ; pattern + timing lines +.loopPattern + lda #$ff ; fill pattern + jsr DrawPattern + bpl .loopPattern + ENDM + +;----------------------------------------------------------- +BlackFunc SUBROUTINE +;----------------------------------------------------------- + _BLACK_FUNC + rts + +;----------------------------------------------------------- + MAC _DRAW_FUNC +;----------------------------------------------------------- +; Draws all function/alignment and timing pattern over existing codewords + +; blacken functions pattern first + jsr BlackFunc + +; draw function pattern + ldy #4 +.loopPattern + lda #$00 ; draw pattern + jsr DrawPattern + bne .loopPattern + +; draw horizontal timing pattern + ldy #QR_SIZE-1 - 9 + ldx #QR_SIZE-1 - 6 +.loopTimingH + jsr InvertPixel + dey + dey + cpy #9 + bcs .loopTimingH + +; draw vertical timing pattern + ldy #6 + ldx #QR_SIZE-1 - 9 +.loopTimingV + jsr InvertPixel + dex + dex + cpx #9 + bcs .loopTimingV + ENDM ; + +;--------------------------------------------------------------- +DrawPattern SUBROUTINE +;--------------------------------------------------------------- +; Y = index, A = fill +.index = tmpVars +.width = tmpVars+1 +.height = tmpVars+2 +.offset = tmpVars+3 +.tmpPat = tmpVars+4 +.fill = tmpVars+5 + + sty .index + sta .fill + ldx PatternY,y + lda PatternHeight,y + sta .height + lda PatternOffset,y + tay +.loopRow + sty .offset + lda PatternStart,y + ora .fill + sta .tmpPat + ldy .index + lda PatternWidth,y + sta .width + lda PatternX,y + tay +; draw one row +.loopBit + sec ; allows for >8 black pixel + ror .tmpPat + bcc .clear +;.set + jsr CheckPixel + bne .skipInvert +.invert + jsr InvertPixel +.skipInvert + iny + dec .width + bne .loopBit + dex + ldy .offset + iny + dec .height + bne .loopRow +.exit + ldy .index + dey + rts + +.clear + jsr CheckPixel + bne .invert + beq .skipInvert + ENDIF ; /QR_DIRECT_DRAW = 0 + ;----------------------------------------------------------- ; Draws the raw codewords (including data and ECC) onto the given QR Code. This requires the initial state of ; the QR Code to be black at function modules and white at codeword modules (including unused remainder bits). MAC _DRAW_CODEWORDS ;----------------------------------------------------------- ; Note: This part has the maximum RAM usage -.right1 = tmpVars+0 -.vert = tmpVars+1 -.j = tmpVars+2 -.y = tmpVars+3 -.iBit = tmpVars+4 -.iByte = tmpVars+5 +.vert = tmpVars+0 +.j = tmpVars+1 +.y = tmpVars+2 + IF QR_DIRECT_DRAW +.iBit = tmpVars+3 +.iByte = tmpVars+4 +.right1 = tmpVars+5 + ELSE +; must not overlap with DrawPattern space +.iBit = tmpVars+6 +.iByte = tmpVars+7 +.right1 = tmpVars+8 + ENDIF ; blacken the (right) function modules in the bitmap IF QR_OVERLAP _BLACK_RIGHT ; returns with X = 0 ELSE + IF QR_DIRECT_DRAW _BLACK_FUNC ; returns with X = 0 + ELSE + jsr BlackFunc + ldx #0 + ENDIF ENDIF ; int i = 0; // Bit index into the data ; ldx #0 @@ -262,6 +402,9 @@ RSMult SUBROUTINE bne .skipBlackMiddle ; blacken the middle function modules in the bitmap _BLACK_MIDDLE + IF QR_DIRECT_DRAW = 0 + ldy .right1 ; DrawPattern overwrites Y + ENDIF .skipBlackMiddle cpy #8+1 bne .skipBlackLeft @@ -332,7 +475,8 @@ RSMult SUBROUTINE ldy .right1 dey dey - bpl .loopRight ; unconditional! +; bpl .loopRight ; unconditional! + jmp .loopRight ; unconditional! ; } // for right .exitDraw ENDM @@ -496,7 +640,7 @@ RSMult SUBROUTINE ;----------------------------------------------------------- MAC _DRAW_FORMAT ;----------------------------------------------------------- - IF QR_SINGLE_MASK = 0 + IF QR_SINGLE_MASK = 0 || QR_DIRECT_DRAW = 0 .idx = tmpVars ldy #QR_FORMATS-1 @@ -510,7 +654,11 @@ RSMult SUBROUTINE lda FormatHi,x and BitMask-8,y .lowFormat + IF QR_DIRECT_DRAW beq .skipFormat + ELSE + bne .skipFormat + ENDIF ldx FormatY1,y lda FormatX1,y tay @@ -643,8 +791,77 @@ _QR_TOTAL SET _QR_TOTAL + . - QRCodeCode ; Add this to your code's data area QRCodeData + IF QR_DIRECT_DRAW = 0 +; Function pattern parameters +PatternX ; left + .byte 6 ; left, vertical timing line + .byte 0, 0, QR_SIZE-8 ; eye pattern + .byte QR_SIZE-8, QR_SIZE-9 ; alignment pattern (right/middle part) + .byte 9 ; top, horizontal timing line +PatternY ; top + .byte QR_SIZE - 10 ; left, vertical timing line + .byte QR_SIZE-1, 7, QR_SIZE-1 ; eye pattern + .byte 8, 8 ; alignment pattern (right/middle part) + .byte QR_SIZE - 7 ; top, horizontal timing line +PatternOffset = . - 1 + .byte EyeLeftTop-PatternStart ; 1 + .byte EyeLeftBottom-PatternStart ; 2 + .byte EyeRightTop-PatternStart ; 3 + .byte AlignRightBottom-PatternStart + .byte AlignRightBottom-PatternStart +; timing lines need no pattern, always drawn filled here +PatternWidth + .byte 1 ; left, vertical timing line + .byte 9, 9, 8 ; eye pattern + .byte 4, 1 ; alignment pattern (right/middle part) + .byte QR_SIZE - 9 - 8 ; top, horizontal timing line +PatternHeight + .byte QR_SIZE - 9 - 8 ; left, vertical timing line + .byte 9, 8, 9 ; eye pattern + .byte 5, 5 ; alignment pattern (right/middle part) + .byte 1 ; top, horizontal timing line + +PatternStart +EyeRightTop + .byte %11111110 + .byte %10000010 + .byte %10111010 + .byte %10111010 + .byte %10111010 + .byte %10000010 + .byte %11111110 + .byte %00000000 + .byte %11111111 +AlignRightBottom = . - 1 +; .byte %1111 + .byte %1000 + .byte %1010 + .byte %1000 + .byte %1111 + +EyeLeftBottom + .byte %00000000 +; .byte %01111111 +; .byte %01000001 +; .byte %01011101 +; .byte %01011101 +; .byte %01011101 +; .byte %01000001 +; .byte %01111111 +EyeLeftTop + .byte %01111111 + .byte %01000001 + .byte %01011101 + .byte %01011101 + .byte %01011101 + .byte %01000001 + .byte %01111111 + .byte %00000000 + .byte %11111111 + ENDIF ; /QR_DIRECT_DRAW + ; Format Information Strings - IF QR_SINGLE_MASK = 0 + IF QR_SINGLE_MASK = 0 || QR_DIRECT_DRAW = 0 IF QR_LEVEL = 0 ; L FormatLo .byte %11101111 @@ -725,7 +942,6 @@ FormatHi .byte %00011000 .byte %01110110 ENDIF - ENDIF ; /QR_SINGLE_MASK = 0 ; positions of the 15 type information bits FormatX1 @@ -742,7 +958,7 @@ FormatX2 ds 7, 8 .byte QR_SIZE-8, QR_SIZE-7, QR_SIZE-6, QR_SIZE-5 .byte QR_SIZE-4, QR_SIZE-3, QR_SIZE-2, QR_SIZE-1 - ENDIF ; QR_SINGLE_MASK = 0 + ENDIF ; /QR_SINGLE_MASK = 0 || QR_DIRECT_DRAW = 0 BitMask .byte $80, $40, $20, $10, $8, $4, $2, $1 diff --git a/QRCodeGenDemo.asm b/QRCodeGenDemo.asm index efad621..36957bc 100644 --- a/QRCodeGenDemo.asm +++ b/QRCodeGenDemo.asm @@ -35,21 +35,31 @@ BASE_ADR = $f000 NTSC = 1 ; 0 = PAL50 +ATARI_2600 = 1 ; enable for Atari 2600 specific code ; QR Code Generator Switches QR_VERSION = 2 ; 1, 2 or 3 (TODO 1 and 3) QR_LEVEL = 1 ; 0 (L), 1 (M), 2 (Q) and 3 (H)) -QR_PADDING = 1 ; (+22 bytes) add padding bytes (optional) -QR_GENERATE = 1 ; (+~12 bytes) generates Reed-Solomon ECC generator polynomial on-the-fly +QR_PADDING = 1 ; (+ 22 bytes) add padding bytes (optional) +QR_GENERATE = 0 ; (+~12 bytes) generates Reed-Solomon ECC generator polynomial on-the-fly ; else uses built-in table -; Atari 2600 specific QR settings (set to 0 for other platforms) +; Atari 2600 specific QR settings (keep set to 0 for other platforms!) + IFCONST ATARI_2600 QR_OVERLAP = 1 ; overlaps input and output data to save RAM (defined for version 2 only!) QR_SINGLE_MASK = 0 ; (-255 bytes) if 1 uses only 1 of the 8 mask pattern +QR_DIRECT_DRAW = 0 ; (+ 45 bytes) draw byte columns instead of individual pixel + ENDIF - IF QR_VERSION = 1 || QR_VERSION = 3 + IF QR_VERSION != 2 ECHO "" - ECHO "ERROR: Version", [QR_VERSION]d, "unsupported by demo code" + ECHO "*** ERROR: Version", [QR_VERSION]d, "unsupported by demo code! ***" + ERR + ENDIF + + IF QR_SINGLE_MASK = 1 && QR_DIRECT_DRAW = 0 + ECHO "" + ECHO "*** ERROR: Unsupported assembler switches combination! ***" ERR ENDIF @@ -76,7 +86,11 @@ random .byte ;--------------------------------------- ; QR code variables ; all byte counts based on version 2, level M QR code + IF QR_DIRECT_DRAW tmpVars ds 6 + ELSE +tmpVars ds 9 + ENDIF msgIdx = tmpVars + 3 IF QR_SINGLE_MASK = 0 @@ -89,11 +103,16 @@ msgData = data + QR_DEGREE ; (QR_MAX_DATA = 28 bytes) ;- - - - - - - - - - - - - - - - - - - - ; The QR code overlaps the data! It overwrites the data while being drawn. IF QR_OVERLAP -qrCodeLst = data + 6 ; all but 6 bytes overlap (version 2 only!) - ds NUM_FIRST + QR_SIZE*3 - QR_TOTAL + 6 ; 38 bytes + IF QR_DIRECT_DRAW +QR_NON_OVER = 6 + ELSE +QR_NON_OVER = 8 + ENDIF +qrCodeLst = data + QR_NON_OVER ; all but 6/8 bytes overlap (version 2 only!) + ds NUM_FIRST + QR_SIZE*3 - QR_TOTAL + QR_NON_OVER ; 38/40 bytes ELSE -qrCodeLst ds NUM_FIRST + QR_SIZE*3 ; 76 bytes - ENDIF +qrCodeLst ds NUM_FIRST + QR_SIZE*3 ; 76 bytes + ENDIF ; /QR_OVERLAP grp0LLst = qrCodeLst + QR_SIZE * 0 firstMsl = qrCodeLst + QR_SIZE * 1 grp1Lst = qrCodeLst + NUM_FIRST + QR_SIZE * 1 @@ -150,62 +169,124 @@ qrGenerator ds QR_DEGREE sta random ; 3 = 14/19 ENDM -; Platform specific macros +; Atari 2600 specific macros IF QR_OVERLAP = 0 + IF QR_DIRECT_DRAW ;----------------------------------------------------------- MAC _BLACK_FUNC ;----------------------------------------------------------- ; blacks all function/alignment and timing pattern - ldx #CODE_LST_SIZE-1 + ldx #CODE_LST_SIZE .loopBlack - lda BlackGfx,x - sta qrCodeLst,x + lda BlackGfx-1,x + sta qrCodeLst-1,x dex - bpl .loopBlack + bne .loopBlack +; X = 0! ENDM + ENDIF - ELSE + ELSE ; QR_OVERLAP ;----------------------------------------------------------- MAC _BLACK_LEFT ;----------------------------------------------------------- -; blacks all function/alignment and timing pattern of the left column +; Blacks all function/alignment and timing pattern areas of the left sprite column + IF QR_DIRECT_DRAW ldx #NUM_FIRST + QR_SIZE-1-8 .loopBlackLeft lda LeftBlack+8,x sta qrCodeLst+8,x dex bpl .loopBlackLeft + ELSE +; clear the bitmap column first... + ldx #NUM_FIRST + QR_SIZE-1-8 + lda #0 +.loopBlackLeft + sta qrCodeLst+8,x + dex + bpl .loopBlackLeft +; ...then draw the pattern + IF QR_DIRECT_DRAW + ldy #1 ; left top pattern/vertical timing line + ELSE + ldy #2 + ENDIF +.loopPattern + lda #$ff ; fill pattern + jsr DrawPattern + bpl .loopPattern + ENDIF ENDM ;----------------------------------------------------------- MAC _BLACK_MIDDLE ;----------------------------------------------------------- -; blacks all function/alignment and timing pattern of the middle column +; Blacks all function/alignment and timing pattern areas of the middle sprite column + IF QR_DIRECT_DRAW ldx #QR_SIZE-1 .loopBlackMiddle lda GRP1Black,x sta grp1Lst,x dex bpl .loopBlackMiddle + ELSE +; clear the bitmap column first... + ldx #QR_SIZE-1 + lda #0 +.loopBlackMiddle + sta grp1Lst,x + dex + bpl .loopBlackMiddle +; ...then draw the pattern + ldy #6 ; align pattern +.loopPattern + lda #$ff ; fill pattern + jsr DrawPattern + cpy #5 + bcs .loopPattern + ENDIF ENDM ;----------------------------------------------------------- MAC _BLACK_RIGHT ;----------------------------------------------------------- -; blacks all function/alignment and timing pattern of the right column +; Blacks all function/alignment and timing pattern areas of the right sprite column + IF QR_DIRECT_DRAW ldx #QR_SIZE .loopBlackRight lda GRP0RBlack-1,x sta grp0RLst-1,x dex bne .loopBlackRight + ELSE +; clear the bitmap column first... + ldx #QR_SIZE-1 + lda #$00 +.loopBlackRight + sta grp0RLst,x + dex + bpl .loopBlackRight +; ...then draw the pattern + ldy #4 ; right pattern +.loopPattern + lda #$ff ; fill pattern + jsr DrawPattern + cpy #3 + bcs .loopPattern + ldx #0 + ENDIF +; X = 0! ENDM + ENDIF ; /QR_OVERLAP + IF QR_DIRECT_DRAW ;----------------------------------------------------------- MAC _DRAW_FUNC ;----------------------------------------------------------- +; Draws all function/alignment and timing pattern over existing codewords ldx #CODE_LST_SIZE-1 .loopBlack lda qrCodeLst,x @@ -222,6 +303,7 @@ qrGenerator ds QR_DEGREE jsr InvertPixel ENDIF ENDM + ENDIF ;=============================================================================== @@ -436,7 +518,7 @@ MessageCode lsr and #$0f tay -; ldy #0 +; ldy #3 lda MessagePtrLo,y sta .msgPtr lda MessagePtrHi,y @@ -486,7 +568,7 @@ CheckPixel SUBROUTINE .notMissile cpy #1+8 bcs .notGRP0L - IF QR_OVERLAP + IF QR_OVERLAP & QR_DIRECT_DRAW cpx #8 ; bottom left eye (partially) shared with data! bcc .alwaysSet ENDIF @@ -560,13 +642,14 @@ _QR_TOTAL SET _QR_TOTAL + . - BitMapCode FunctionModulesData ; Platform and version specific function module data definition + IF QR_DIRECT_DRAW IF QR_VERSION = 1 ERR ; TODO ENDIF IF QR_VERSION = 2 IF QR_SINGLE_MASK - include FuncDataV2S.inc ; TODO: special pattern + include FuncDataV2S.inc ; special pattern ELSE include FuncDataV2.inc ENDIF @@ -574,6 +657,19 @@ FunctionModulesData IF QR_VERSION = 3 ERR ; TODO + ENDIF + ENDIF + +FirstIdxTbl ; for 25 pixel + ds 7, 0 + .byte $fe + ds 7, 0 + .byte $01 + ds 7, 0 + IF QR_LEVEL = 0 || QR_LEVEL = 1 + .byte $bf ; 1st format bit is 1 + ELSE + .byte $3f ; 1st format bit is 0 ENDIF ECHO "QR Code function modules data:", [. - FunctionModulesData]d, "bytes" @@ -584,12 +680,11 @@ _QR_TOTAL SET _QR_TOTAL + . - FunctionModulesData .byte " QR Code Generator Demo V0.3 - (C)2021 Thomas Jentzsch " ; messages MUST NOT be longer than 26 bytes for version 2, level M! -; Galadriel: MessageTbl Message0 .byte "2002 - Thrust+ Platinum" Message1 - .byte "2001 - Jammed " + .byte "2001 - Jammed" Message2 .byte "2005 - SWOOPS!" Message3 diff --git a/README.md b/README.md index fc85cc1..58dcb77 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,14 @@ If you make use of my code or have questions, please let me know. - code size optimized for minimal RAM and ROM space - all eight mask pattern supported - Atari 2600 demo code (randomly generates some Atari 2600 related messages) -- generator code for Reed-Solomon ECC generator polygons accompanied (can be integrated to compute on-the-fly) +- generator code for Reed-Solomon ECC generator polygons accompanied (can also be integrated to compute on-the-fly) ## Limitations -- only small, single block QR codes supported +- only small (versions 1, 2 and 3), single block QR codes supported - only byte mode supported - no automatic mask pattern evaluation - tested only for version 2 QR codes (25x25) -- memory organization and pixel checking/drawing has to be implemented platform specific; this includes the finder, alignment and timing pattern +- memory organization has to be implemented platform specific ## License Copyright © 2021 Thomas Jentzsch. (GPLV3 License)