Skip to content

Commit

Permalink
Allow read/write of pixels in bitmaps
Browse files Browse the repository at this point in the history
This change adds the ability to set and read individual pixels in a Bitmap. It
adds a type called `BitmapView` that is supposed to represent any type that
allows for this. Right now, that's just `DisplayFrame` and `BitmapData`.
  • Loading branch information
Nycto authored Jul 12, 2023
1 parent c297918 commit a89b4d4
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 28 deletions.
75 changes: 50 additions & 25 deletions src/playdate/graphics.nim
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,31 @@ proc load*(this: LCDBitmap, path: string) {.raises: [IOError]} =
type BitmapData* = ref object
width*: int
height*: int
bytes*: int
rowbytes: int
data: ptr UncheckedArray[uint8]

proc index(x, y, rowbytes: int): int = y * rowbytes + x div 8
## Returns the index of an (x, y) coordinate in a flattened array.

template read(bitmap: BitmapData, x, y: int): untyped =
## Read a pixel from a bitmap.
assert(bitmap.data != nil)
bitmap.data[index(x, y, bitmap.rowbytes)]

proc getData*(this: LCDBitmap): BitmapData =
## Fetch the underlying bitmap data for an image.
privateAccess(PlaydateGraphics)
assert(this != nil)
assert(this.resource != nil)
var bitmapData = BitmapData()
var width, height, bytes: cint
playdate.graphics.getBitmapData(this.resource, addr(width), addr(height), addr(bytes),
nil, nil)
bitmapData.width = width.int
bitmapData.height = height.int
bitmapData.bytes = bytes.int
playdate.graphics.getBitmapData(
this.resource,
cast[ptr cint](addr(bitmapData.width)),
cast[ptr cint](addr(bitmapData.height)),
cast[ptr cint](addr(bitmapData.rowbytes)),
nil,
cast[ptr ptr uint8](addr(bitmapData.data))
)
return bitmapData

proc clear*(this: LCDBitmap, color: LCDColor) =
Expand Down Expand Up @@ -232,8 +246,12 @@ proc getTextWidth*(this: LCDFont, text: string, len: int, encoding: PDStringEnco
privateAccess(LCDFont)
return playdate.graphics.getTextWidth(this.resource, text.cstring, len.csize_t, encoding, tracking.cint)

type DisplayFrame* = ptr array[LCD_ROWSIZE * LCD_ROWS, uint8]
# type DisplayFrame* = ref DisplayFrameObj
type
DisplayFrame* = distinct ptr array[LCD_ROWSIZE * LCD_ROWS, uint8]
## The raw bytes in a display frame buffer.

BitmapView* = DisplayFrame | BitmapData
## Types that allow the manipulation of individual pixels.

proc getFrame*(this: ptr PlaydateGraphics): DisplayFrame =
privateAccess(PlaydateGraphics)
Expand All @@ -243,38 +261,45 @@ proc getDisplayFrame*(this: ptr PlaydateGraphics): DisplayFrame =
privateAccess(PlaydateGraphics)
return cast[DisplayFrame](this.getDisplayFrame()) # Who should manage this memory? Not clear. Not auto-managed right now.

proc frameIndex(x, y: int): int {.inline.} =
## Returns the index of a coordinate within a DisplayFrame.
y * LCD_ROWSIZE + x div 8
proc width*(frame: DisplayFrame): auto {.inline.} = LCD_COLUMNS
## Return the width of the display frame buffer.

proc height*(frame: DisplayFrame): auto {.inline.} = LCD_ROWS
## Return the height of the display frame buffer.

template read(frame: DisplayFrame, x, y: int): untyped =
## Read a pixel from a display frame buffer
assert(cast[pointer](frame) != nil)
cast[ptr array[LCD_ROWSIZE * LCD_ROWS, uint8]](frame)[index(x, y, LCD_ROWSIZE)]

proc frameBit(x: int): uint8 {.inline.} =
proc viewBit(x: int): uint8 {.inline.} =
## Returns the specific packed bit that is used to represent an `x` coordinate.
1'u8 shl uint8(7 - (x mod 8))

proc isInFrame(x, y: int): bool {.inline.} =
proc isInView(view: BitmapView, x, y: int): bool {.inline.} =
## Returns whether a point is within the frame.
x >= 0 and y >= 0 and x < LCD_COLUMNS and y < LCD_ROWS
x >= 0 and y >= 0 and x < view.width and y < view.height

proc get*(frame: DisplayFrame, x, y: int): LCDSolidColor =
proc get*(view: BitmapView, x, y: int): LCDSolidColor =
## Returns the color of a pixel at the given coordinate.
if not isInFrame(x, y) or (frame[frameIndex(x, y)] and frameBit(x)) != 0:
if not view.isInView(x, y) or (view.read(x, y) and viewBit(x)) != 0:
kColorWhite
else:
kColorBlack

proc set*(frame: DisplayFrame, x, y: int) =
proc set*(view: var BitmapView, x, y: int) =
## Sets the pixel at x, y to black.
if isInFrame(x, y):
frame[frameIndex(x, y)] = frame[frameIndex(x, y)] and not frameBit(x)
if view.isInView(x, y):
view.read(x, y) = view.read(x, y) and not viewBit(x)

proc clear*(frame: DisplayFrame, x, y: int) =
proc clear*(view: var BitmapView, x, y: int) =
## Clears the color from a pixel at the given coordinate.
if isInFrame(x, y):
frame[frameIndex(x, y)] = frame[frameIndex(x, y)] or frameBit(x)
if view.isInView(x, y):
view.read(x, y) = view.read(x, y) or viewBit(x)

proc set*(frame: DisplayFrame, x, y: int, color: LCDSolidColor) =
proc set*(view: var BitmapView, x, y: int, color: LCDSolidColor) =
## Sets the specific color of a pixel at the given coordinate.
if (color == kColorBlack): set(frame, x, y) else: clear(frame, x, y)
if (color == kColorBlack): set(view, x, y) else: clear(view, x, y)

proc getDebugBitmap*(this: ptr PlaydateGraphics): LCDBitmap =
privateAccess(PlaydateGraphics)
Expand Down
9 changes: 6 additions & 3 deletions tests/t_graphics.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ proc execGraphicsTests*(runnable: bool) =

suite "Graphics API":

template frameTests(create: untyped) =
template bitviewTests(create: untyped) =
if runnable:
var value = create
discard value.get(1, 1)
Expand All @@ -17,10 +17,13 @@ proc execGraphicsTests*(runnable: bool) =
graphics.set(value, 0, 0, kColorBlack)

test "Setting Frame bits should compile":
frameTests(playdate.graphics.getFrame())
bitviewTests(playdate.graphics.getFrame())

test "Setting DisplayFrame bits should compile":
frameTests(playdate.graphics.getDisplayFrame())
bitviewTests(playdate.graphics.getDisplayFrame())

test "Setting Bitmap bits should compile":
bitviewTests(playdate.graphics.newBitmap(10, 10, kColorWhite).getData)

template colorTests(colorOrPattern: untyped) =
if runnable:
Expand Down

0 comments on commit a89b4d4

Please sign in to comment.