Compare commits

...

11 commits

Author SHA1 Message Date
8458981007 Add support for decoding images from a pointer and a length instead of a string (TODO: qoi, ppm)
Some checks failed
Github Actions / build (1.4.x, ubuntu-latest) (push) Has been cancelled
Github Actions / build (1.4.x, windows-latest) (push) Has been cancelled
Github Actions / build (stable, ubuntu-latest) (push) Has been cancelled
Github Actions / build (stable, windows-latest) (push) Has been cancelled
2024-08-17 17:31:56 +02:00
89ac8dfbe6 Fix compatibility with nimskull 2024-08-17 17:28:15 +02:00
Andre von Houck
3b65ca5605
Merge pull request #552 from Nimaoth/master
added support for open type format 12
2024-04-28 11:54:11 -07:00
Nimaoth
f19271867f fixed typo 2024-04-14 20:47:17 +02:00
Nimaoth
7ab1cc7b66 bumped version 2024-04-14 20:45:27 +02:00
Nimaoth
0c4d11809e removed line to save test image 2024-04-14 20:41:50 +02:00
Nimaoth
66125c2b5d added support for open type format 12 2024-04-14 20:34:39 +02:00
Andre von Houck
2ce0404922
Merge pull request #547 from simonkrauter/patch-2
Extend example text.nim
2023-10-04 17:17:27 -07:00
Andre von Houck
18db66aeb0
Merge pull request #546 from simonkrauter/patch-1
Fix documentation in contexts.nim
2023-10-04 17:16:20 -07:00
Simon Krauter
2ef52dc880
Extend example text.nim
Set the font color. (Did not find this in the documentation.)
2023-10-02 21:56:49 -03:00
Simon Krauter
7d6d84b610
Fix documentation in contexts.nim 2023-10-02 21:34:18 -03:00
13 changed files with 143 additions and 36 deletions

View file

@ -5,6 +5,7 @@ image.fill(rgba(255, 255, 255, 255))
var font = readFont("examples/data/Roboto-Regular_1.ttf")
font.size = 20
font.paint.color = color(1, 0, 0)
let text = "Typesetting is the arrangement and composition of text in graphic design and publishing in both digital and traditional medias."

View file

@ -1,4 +1,4 @@
version = "5.0.6"
version = "5.0.7"
author = "Andre von Houck and Ryan Oldenburg"
description = "Full-featured 2d graphics library for Nim."
license = "MIT"

View file

@ -49,8 +49,39 @@ proc decodeImageDimensions*(
## Decodes an image's dimensions from memory.
decodeImageDimensions(data.cstring, data.len)
template compare_as(T: typedesc, p,q: pointer): bool =
cast[ptr T](p)[] == cast[ptr T](q)[]
proc decodeImage*(data: pointer, len: int): Image {.raises: [PixieError].} =
## Loads an image from memory, from a pointer and a length.
if len > 8 and compare_as(uint64, data, pngSignature.addr):
decodePng(data, len).convertToImage()
elif len > 2 and compare_as(uint16, data, jpegStartOfImage.addr):
decodeJpeg(data, len)
elif len > 2 and compare_as(array[2,char], data, bmpSignature.cstring):
decodeBmp(data, len)
elif len > 5 and
compare_as(array[5,char], data, xmlSignature.cstring) or
compare_as(array[4,char], data, svgSignature.cstring):
# TODO: avoid allocating/initializing string
var s = newStringOfCap(len)
s.setLen len
copyMem(s.cstring, data, len)
newImage(parseSvg(s))
elif len > 6 and compare_as(array[6,char], data, gifSignatures[0].cstring) or
compare_as(array[6,char], data, gifSignatures[1].cstring):
newImage(decodeGif(data, len))
# TODO
# elif len > (14+8) and compare_as(array[4,char], data, qoiSignature.cstring):
# decodeQoi(data, len).convertToImage()
# elif len > 9 and compare_as(array[2,char], data, ppmSignatures[0].cstring) or
# compare_as(array[2,char], data, ppmSignatures[1].cstring):
# decodePpm(data, len)
else:
raise newException(PixieError, "Unsupported image file format")
proc decodeImage*(data: string): Image {.raises: [PixieError].} =
## Loads an image from memory.
## Loads an image from memory as a string.
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
decodePng(data).convertToImage()
elif data.len > 2 and data.readUint16(0) == cast[uint16](jpegStartOfImage):

View file

@ -481,8 +481,8 @@ proc fillText*(ctx: Context, text: string, at: Vec2) {.raises: [PixieError].} =
proc fillText*(
ctx: Context, text: string, x, y: float32
) {.inline, raises: [PixieError].} =
## Draws the outlines of the characters of a text string at the specified
## coordinates.
## Draws a text string at the specified coordinates, filling the string's
## characters with the current fillStyle
ctx.fillText(text, vec2(x, y))
proc strokeText*(ctx: Context, text: string, at: Vec2) {.raises: [PixieError].} =

View file

@ -227,6 +227,17 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
decodeDib(data[14].unsafeAddr, data.len - 14)
proc decodeBmp*(data: pointer, len: int): Image {.raises: [PixieError].} =
## Decodes bitmap data into an image.
if len < 14:
failInvalid()
# BMP Header
if cast[ptr int16](data)[] != cast[ptr int16]("BM".cstring)[]:
failInvalid()
decodeDib(cast[ptr UncheckedArray[char]](data)[14].addr, len - 14)
proc decodeBmpDimensions*(
data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} =

View file

@ -22,12 +22,18 @@ template failInvalid() =
when defined(release):
{.push checks: off.}
proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
template compare_as(T: typedesc, p,q: pointer): bool =
cast[ptr T](p)[] == cast[ptr T](q)[]
proc decodeGif*(data: pointer, len: int): Gif {.raises: [PixieError].} =
let data = cast[ptr UncheckedArray[uint8]](data)
## Decodes GIF data.
if data.len < 13:
if len < 13:
failInvalid()
if data[0 .. 5] notin gifSignatures:
if not (len > 6 and
compare_as(array[6,char], data, gifSignatures[0].cstring) or
compare_as(array[6,char], data, gifSignatures[1].cstring)):
raise newException(PixieError, "Invalid GIF file signature")
result = Gif()
@ -49,7 +55,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
var pos = 13
if pos + globalColorTableSize * 3 > data.len:
if pos + globalColorTableSize * 3 > len:
failInvalid()
var
@ -69,7 +75,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
proc skipSubBlocks() =
while true: # Skip data sub-blocks
if pos + 1 > data.len:
if pos + 1 > len:
failInvalid()
let subBlockSize = data.readUint8(pos).int
@ -82,7 +88,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
var controlExtension: ControlExtension
while true:
if pos + 1 > data.len:
if pos + 1 > len:
failInvalid()
let blockType = data.readUint8(pos)
@ -90,7 +96,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
case blockType:
of 0x2c: # Image
if pos + 9 > data.len:
if pos + 9 > len:
failInvalid()
let
@ -108,7 +114,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
if imageWidth > screenWidth or imageHeight > screenHeight:
raise newException(PixieError, "Invalid GIF frame dimensions")
if pos + localColorTableSize * 3 > data.len:
if pos + localColorTableSize * 3 > len:
failInvalid()
var localColorTable: seq[ColorRGBX]
@ -123,7 +129,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
)
pos += 3
if pos + 1 > data.len:
if pos + 1 > len:
failInvalid()
let minCodeSize = data.readUint8(pos).int
@ -135,7 +141,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
# The image data is contained in a sequence of sub-blocks
var lzwDataBlocks: seq[(int, int)] # (offset, len)
while true:
if pos + 1 > data.len:
if pos + 1 > len:
failInvalid()
let subBlockSize = data.readUint8(pos).int
@ -144,7 +150,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
if subBlockSize == 0:
break
if pos + subBlockSize > data.len:
if pos + subBlockSize > len:
failInvalid()
lzwDataBlocks.add((pos, subBlockSize))
@ -307,7 +313,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
controlExtension = ControlExtension()
of 0x21: # Extension
if pos + 1 > data.len:
if pos + 1 > len:
failInvalid()
let extensionType = data.readUint8(pos + 0)
@ -316,7 +322,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
case extensionType:
of 0xf9:
# Graphic Control Extension
if pos + 1 > data.len:
if pos + 1 > len:
failInvalid()
let blockSize = data.readUint8(pos).int
@ -325,7 +331,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
if blockSize != 4:
failInvalid()
if pos + blockSize > data.len:
if pos + blockSize > len:
failInvalid()
controlExtension.fields = data.readUint8(pos + 0)
@ -344,7 +350,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
of 0xff:
# Application Specific
if pos + 1 > data.len:
if pos + 1 > len:
failInvalid()
let blockSize = data.readUint8(pos).int
@ -353,7 +359,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
if blockSize != 11:
failInvalid()
if pos + blockSize > data.len:
if pos + blockSize > len:
failInvalid()
pos += blockSize
@ -378,6 +384,9 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
for interval in result.intervals:
result.duration += interval
proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
return decodeGif(data.cstring, data.len)
proc decodeGifDimensions*(
data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} =

View file

@ -1,5 +1,5 @@
import chroma, flatty/binny, ../common, ../images, ../internal,
../simd, std/decls, std/sequtils, std/strutils
../simd, std/sequtils, std/strutils
# This JPEG decoder is loosely based on stb_image which is public domain.
@ -547,7 +547,7 @@ proc fillBitBuffer(state: var DecoderState) =
proc huffmanDecode(state: var DecoderState, tableCurrent, table: int): uint8 =
## Decode a uint8 from the huffman table.
var huffman {.byaddr.} = state.huffmanTables[tableCurrent][table]
var huffman = state.huffmanTables[tableCurrent][table].addr
state.fillBitBuffer()
@ -884,14 +884,15 @@ proc idctBlock(component: var Component, offset: int, data: array[64, int16]) =
proc decodeBlock(state: var DecoderState, comp, row, column: int) =
## Decodes a block.
var data {.byaddr.} = state.components[comp].blocks[row][column]
var data = state.components[comp].blocks[row][column].addr
if state.progressive:
if state.spectralStart == 0:
state.decodeProgressiveBlock(comp, data)
state.decodeProgressiveBlock(comp, data[])
else:
state.decodeProgressiveContinuationBlock(comp, data)
state.decodeProgressiveContinuationBlock(comp, data[])
else:
state.decodeRegularBlock(comp, data)
state.decodeRegularBlock(comp, data[])
proc checkRestart(state: var DecoderState) =
## Check if we might have run into a restart marker, then deal with it.
@ -941,7 +942,7 @@ proc quantizationAndIDCTPass(state: var DecoderState) =
failInvalid()
for column in 0 ..< h:
for row in 0 ..< w:
var data {.byaddr.} = state.components[comp].blocks[row][column]
var data = state.components[comp].blocks[row][column].addr
when defined(amd64) and allowSimd:
for i in 0 ..< 8: # 8 per pass
@ -957,7 +958,7 @@ proc quantizationAndIDCTPass(state: var DecoderState) =
state.components[comp].idctBlock(
state.components[comp].widthStride * column * 8 + row * 8,
data
data[]
)
proc magnifyXBy2(mask: Mask): Mask =
@ -1091,12 +1092,12 @@ proc buildImage(state: var DecoderState): Image =
else:
failInvalid("invalid orientation")
proc decodeJpeg*(data: string): Image {.raises: [PixieError].} =
proc decodeJpeg*(data: pointer, len: int): Image {.raises: [PixieError].} =
## Decodes the JPEG into an Image.
var state = DecoderState()
state.buffer = cast[ptr UncheckedArray[uint8]](data.cstring)
state.len = data.len
state.buffer = cast[ptr UncheckedArray[uint8]](data)
state.len = len
while true:
if state.readUint8() != 0xFF:
@ -1156,6 +1157,10 @@ proc decodeJpeg*(data: string): Image {.raises: [PixieError].} =
state.buildImage()
proc decodeJpeg*(data: string): Image {.raises: [PixieError].} =
## Decodes the JPEG into an Image.
decodeJpeg(data.cstring, data.len)
proc decodeJpegDimensions*(
data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} =

View file

@ -455,6 +455,7 @@ proc parseCmapTable(buf: string, offset: int): CmapTable =
let format = buf.readUint16(i + 0).swap()
if format == 4:
# https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-4-segment-mapping-to-delta-values
type Format4 = object
format: uint16
length: uint16
@ -518,8 +519,46 @@ proc parseCmapTable(buf: string, offset: int): CmapTable =
if c != 65535:
result.runeToGlyphId[Rune(c)] = glyphId.uint16
result.glyphIdToRune[glyphId.uint16] = Rune(c)
elif format == 12:
# https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-12-segmented-coverage
type Format12 = object
format: uint16
reserved: uint16
length: uint32
language: uint32
numGroups: uint32
buf.eofCheck(i + 16)
var subTable: Format12
subTable.format = format
subTable.reserved = buf.readUint16(i + 2).swap()
subTable.length = buf.readUint32(i + 4).swap()
subTable.language = buf.readUint32(i + 8).swap()
subTable.numGroups = buf.readUint32(i + 12).swap()
i += 16
buf.eofCheck(i + subTable.numGroups.int * 12)
for k in 0 ..< subTable.numGroups:
let startCharCode = buf.readUint32(i + 0).swap()
let endCharCode = buf.readUint32(i + 4).swap()
let startGlyphId = buf.readUint32(i + 8).swap()
for c in startCharCode .. endCharCode:
let glyphId = startGlyphId + (c - startCharCode)
if glyphId > uint16.high:
# TODO: currently only 16 bit glyph ids are supported
raise newException(PixieError, "Found glyph outside of uint16 range: " & $glyphId)
result.runeToGlyphId[Rune(c)] = uint16(glyphId)
result.glyphIdToRune[uint16(glyphId)] = Rune(c)
i += 12
else:
# TODO implement other Windows encodingIDs
# TODO implement other windows formats
discard
else:
# TODO implement other cmap platformIDs

View file

@ -497,7 +497,6 @@ proc blendRect(a, b: Image, pos: Ivec2, blendMode: BlendMode) =
xEnd - xStart
)
of MaskBlend:
{.linearScanEnd.}
if yStart + py > 0:
zeroMem(a.data[0].addr, (yStart + py) * a.width * 4)
for y in yStart ..< yEnd:
@ -607,7 +606,6 @@ proc drawSmooth(a, b: Image, transform: Mat3, blendMode: BlendMode) =
)
of MaskBlend:
{.linearScanEnd.}
if blendMode == MaskBlend and xStart > 0:
zeroMem(a.data[a.dataIndex(0, y)].addr, xStart * 4)

View file

@ -1505,7 +1505,6 @@ proc fillCoverage(
)
of MaskBlend:
{.linearScanEnd.}
blendLineCoverageMask(
image.getUncheckedArray(startX, y),
cast[ptr UncheckedArray[uint8]](coverages[0].unsafeAddr),
@ -1560,7 +1559,6 @@ proc fillHits(
blendLineNormal(image.getUncheckedArray(start, y), rgbx, len)
of MaskBlend:
{.linearScanEnd.}
var filledTo = startX
for (start, len) in hits.walkInteger(numHits, windingRule, y, image.width):
if maskClears: # Clear any gap between this fill and the previous fill

BIN
tests/fonts/NotoEmoji.otf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View file

@ -4,6 +4,21 @@ proc wh(image: Image): Vec2 =
## Return with and height as a size vector.
vec2(image.width.float32, image.height.float32)
block:
var font = readFont("tests/fonts/NotoEmoji.otf")
font.size = 26
let image = newImage(800, 300)
image.fill(rgba(255, 255, 255, 255))
image.fillText(font, """
🚑🐑👭🔉🚷🦣💆🔁💺🚵🕦🔦🗓🦟😶🦄⌛🍙😄🇽
🐠💓🦗🎭🏛🔴🫕🧶🍖🦁🏋🌗🛬🕐💡👉🎯🕔🚏🚲
🐵🎍💳🥬🟦🪘📠📊🎧🎦🎁🌌🪲🦩🤢☎🚺🚾👺🚃
🐨🌆🥉💭🗳🦵🟪📆🥮⏯🩴💷🦲➗🌶🧜🖖⏰🛗🔻
📁🧞😃🌴🚶󾠫🦙🔎⏲🔵🖐☦😪🌯🙆🇺😂🍅🇿🚟🤜
📼👰🍁📽☪🔄🤝🔧🦸🏰🏳🔜🎥🚋🇫🦨🏜🆖🏤🪖⏏""")
image.xray("tests/fonts/masters/emoji.png")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24