about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/endians2.nim186
-rw-r--r--src/img/png.nim93
-rw-r--r--src/io/request.nim2
-rw-r--r--src/types/color.nim18
-rw-r--r--src/utils/endians.nim39
5 files changed, 126 insertions, 212 deletions
diff --git a/lib/endians2.nim b/lib/endians2.nim
deleted file mode 100644
index c9a39144..00000000
--- a/lib/endians2.nim
+++ /dev/null
@@ -1,186 +0,0 @@
-# Copyright (c) 2018-2019 Status Research & Development GmbH
-# Licensed and distributed under either of
-#   * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
-#   * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
-# at your option. This file may not be copied, modified, or distributed except according to those terms.
-
-# Endian conversion operations for unsigned integers, suitable for serializing
-# and deserializing data. The operations are only defined for unsigned
-# integers - if you wish to encode signed integers, convert / cast them to
-# unsigned first!
-#
-# Although it would be possible to enforce correctness with endians in the type
-# (`BigEndian[uin64]`) this seems like overkill. That said, some
-# static analysis tools allow you to annotate fields with endianness - perhaps
-# an idea for the future, akin to `TaintedString`?
-#
-# Keeping the above in mind, it's generally safer to use `array[N, byte]` to
-# hold values of specific endianness and read them out with `fromBytes` when the
-# integer interpretation of the bytes is needed.
-
-{.push raises: [].}
-
-type
-  SomeEndianInt* = uint8|uint16|uint32|uint64
-    ## types that we support endian conversions for - uint8 is there for
-    ## for syntactic / generic convenience. Other candidates:
-    ## * int/uint - uncertain size, thus less suitable for binary interop
-    ## * intX - over and underflow protection in nim might easily cause issues -
-    ##          need to consider before adding here
-
-const
-  useBuiltins = not defined(noIntrinsicsEndians)
-
-when (defined(gcc) or defined(llvm_gcc) or defined(clang)) and useBuiltins:
-  func swapBytesBuiltin(x: uint8): uint8 = x
-  func swapBytesBuiltin(x: uint16): uint16 {.
-      importc: "__builtin_bswap16", nodecl.}
-
-  func swapBytesBuiltin(x: uint32): uint32 {.
-      importc: "__builtin_bswap32", nodecl.}
-
-  func swapBytesBuiltin(x: uint64): uint64 {.
-      importc: "__builtin_bswap64", nodecl.}
-
-elif defined(icc) and useBuiltins:
-  func swapBytesBuiltin(x: uint8): uint8 = x
-  func swapBytesBuiltin(a: uint16): uint16 {.importc: "_bswap16", nodecl.}
-  func swapBytesBuiltin(a: uint32): uint32 {.importc: "_bswap", nodec.}
-  func swapBytesBuiltin(a: uint64): uint64 {.importc: "_bswap64", nodecl.}
-
-elif defined(vcc) and useBuiltins:
-  func swapBytesBuiltin(x: uint8): uint8 = x
-  func swapBytesBuiltin(a: uint16): uint16 {.
-      importc: "_byteswap_ushort", cdecl, header: "<intrin.h>".}
-
-  func swapBytesBuiltin(a: uint32): uint32 {.
-      importc: "_byteswap_ulong", cdecl, header: "<intrin.h>".}
-
-  func swapBytesBuiltin(a: uint64): uint64 {.
-      importc: "_byteswap_uint64", cdecl, header: "<intrin.h>".}
-
-func swapBytesNim(x: uint8): uint8 = x
-func swapBytesNim(x: uint16): uint16 = (x shl 8) or (x shr 8)
-
-func swapBytesNim(x: uint32): uint32 =
-  let v = (x shl 16) or (x shr 16)
-
-  ((v shl 8) and 0xff00ff00'u32) or ((v shr 8) and 0x00ff00ff'u32)
-
-func swapBytesNim(x: uint64): uint64 =
-  var v = (x shl 32) or (x shr 32)
-  v =
-    ((v and 0x0000ffff0000ffff'u64) shl 16) or
-    ((v and 0xffff0000ffff0000'u64) shr 16)
-
-  ((v and 0x00ff00ff00ff00ff'u64) shl 8) or
-    ((v and 0xff00ff00ff00ff00'u64) shr 8)
-
-func swapBytes*[T: SomeEndianInt](x: T): T {.inline.} =
-  ## Reverse the bytes within an integer, such that the most significant byte
-  ## changes place with the least significant one, etc
-  ##
-  ## Example:
-  ## doAssert swapBytes(0x01234567'u32) == 0x67452301
-  when nimvm:
-    swapBytesNim(x)
-  else:
-    when declared(swapBytesBuiltin):
-      swapBytesBuiltin(x)
-    else:
-      swapBytesNim(x)
-
-func toBytes*(x: SomeEndianInt, endian: Endianness = system.cpuEndian):
-    array[sizeof(x), byte] {.noinit, inline.} =
-  ## Convert integer to its corresponding byte sequence using the chosen
-  ## endianness. By default, native endianness is used which is not portable!
-  let v =
-    if endian == system.cpuEndian: x
-    else: swapBytes(x)
-
-  when nimvm: # No copyMem in vm
-    for i in 0..<sizeof(result):
-      result[i] = byte((v shr (i * 8)) and 0xff)
-  else:
-    copyMem(addr result, unsafeAddr v, sizeof(result))
-
-func toBytesLE*(x: SomeEndianInt):
-    array[sizeof(x), byte] {.inline.} =
-  ## Convert a native endian integer to a little endian byte sequence
-  toBytes(x, littleEndian)
-
-func toBytesBE*(x: SomeEndianInt):
-    array[sizeof(x), byte] {.inline.} =
-  ## Convert a native endian integer to a native endian byte sequence
-  toBytes(x, bigEndian)
-
-func fromBytes*(
-    T: typedesc[SomeEndianInt],
-    x: openArray[byte],
-    endian: Endianness = system.cpuEndian): T {.inline.} =
-  ## Read bytes and convert to an integer according to the given endianness.
-  ##
-  ## Note: The default value of `system.cpuEndian` is not portable across
-  ## machines.
-  ##
-  ## Panics when `x.len < sizeof(T)` - for shorter buffers, copy the data to
-  ## an `array` first using `arrayops.initCopyFrom`, taking care to zero-fill
-  ## at the right end - usually the beginning for big endian and the end for
-  ## little endian, but this depends on the serialization of the bytes.
-
-  # This check gets optimized away when the compiler can prove that the length
-  # is large enough - passing in an `array` or using a construct like
-  # ` toOpenArray(pos, pos + sizeof(T) - 1)` are two ways that this happens
-  doAssert x.len >= sizeof(T), "Not enough bytes for endian conversion"
-
-  when nimvm: # No copyMem in vm
-    for i in 0..<sizeof(result):
-      result = result or (T(x[i]) shl (i * 8))
-  else:
-    # `copyMem` helps compilers optimize the copy into a single instruction, when
-    # alignment etc permits
-    copyMem(addr result, unsafeAddr x[0], sizeof(result))
-
-  if endian != system.cpuEndian:
-    # The swap is turned into a CPU-specific instruction and/or combined with
-    # the copy above, again when conditions permit it - for example, on X86
-    # fromBytesBE gets compiled into a single `MOVBE` instruction
-    result = swapBytes(result)
-
-func fromBytesBE*(
-    T: typedesc[SomeEndianInt],
-    x: openArray[byte]): T {.inline.} =
-  ## Read big endian bytes and convert to an integer. At runtime, v must contain
-  ## at least sizeof(T) bytes. By default, native endianness is used which is
-  ## not portable!
-  fromBytes(T, x, bigEndian)
-
-func toBE*[T: SomeEndianInt](x: T): T {.inline.} =
-  ## Convert a native endian value to big endian. Consider toBytesBE instead
-  ## which may prevent some confusion.
-  if cpuEndian == bigEndian: x
-  else: x.swapBytes
-
-func fromBE*[T: SomeEndianInt](x: T): T {.inline.} =
-  ## Read a big endian value and return the corresponding native endian
-  # there's no difference between this and toBE, except when reading the code
-  toBE(x)
-
-func fromBytesLE*(
-    T: typedesc[SomeEndianInt],
-    x: openArray[byte]): T {.inline.} =
-  ## Read little endian bytes and convert to an integer. At runtime, v must
-  ## contain at least sizeof(T) bytes. By default, native endianness is used
-  ## which is not portable!
-  fromBytes(T, x, littleEndian)
-
-func toLE*[T: SomeEndianInt](x: T): T {.inline.} =
-  ## Convert a native endian value to little endian. Consider toBytesLE instead
-  ## which may prevent some confusion.
-  if cpuEndian == littleEndian: x
-  else: x.swapBytes
-
-func fromLE*[T: SomeEndianInt](x: T): T {.inline.} =
-  ## Read a little endian value and return the corresponding native endian
-  # there's no difference between this and toLE, except when reading the code
-  toLE(x)
diff --git a/src/img/png.nim b/src/img/png.nim
index 9bcbd348..50b0e980 100644
--- a/src/img/png.nim
+++ b/src/img/png.nim
@@ -3,8 +3,7 @@ import math
 import bindings/zlib
 import img/bitmap
 import types/color
-
-import lib/endians2
+import utils/endians
 
 type PNGWriter = object
   buf: pointer
@@ -50,24 +49,21 @@ type PNGColorType {.size: sizeof(uint8).} = enum
   GRAYSCALE_WITH_ALPHA = 4
   TRUECOLOR_WITH_ALPHA = 6
 
-func u8toc(x: openArray[uint8]): string =
-  #TODO ew
-  var s = newString(x.len)
-  copyMem(addr s[0], unsafeAddr x[0], x.len)
-  return s
-
 const PNGSignature = "\x89PNG\r\n\x1A\n"
 proc writeIHDR(writer: var PNGWriter, width, height: uint32,
     bitDepth: uint8, colorType: PNGColorType,
     compressionMethod, filterMethod, interlaceMethod: uint8) =
   writer.writeStr(PNGSignature)
-  let ihdr = u8toc(pngInt(width)) &
-    u8toc(pngInt(height)) &
-    char(bitDepth) &
-    char(uint8(colorType)) &
-    char(compressionMethod) &
-    char(filterMethod) &
-    char(interlaceMethod)
+  var ihdr {.noInit.}: array[13, uint8]
+  var pw = pngInt(width)
+  var ph = pngInt(height)
+  copyMem(addr ihdr[0], addr pw[0], 4)
+  copyMem(addr ihdr[4], addr ph[0], 4)
+  ihdr[8] = bitDepth
+  ihdr[9] = uint8(colorType)
+  ihdr[10] = compressionMethod
+  ihdr[11] = filterMethod
+  ihdr[12] = interlaceMethod
   writer.writeChunk("IHDR", ihdr)
 
 proc writeIDAT(writer: var PNGWriter, bmp: Bitmap) =
@@ -121,6 +117,9 @@ type PNGReader = object
   strm: z_stream
   strmend: bool
   atline: int
+  plteseen: bool
+  palette: seq[RGBAColor]
+  trns: RGBAColor
 
 func width(reader: PNGReader): int {.inline.} = int(reader.bmp.width)
 
@@ -165,7 +164,7 @@ template readU8(reader: var PNGReader): uint8 =
 template readU32(reader: var PNGReader): uint32 =
   if reader.i + 4 > reader.limit:
     reader.err "too short"
-  let x = fromBytesBE(uint32, toOpenArray(reader.iq, reader.i, reader.i + 3))
+  let x = fromBytesBEu32(toOpenArray(reader.iq, reader.i, reader.i + 3))
   reader.i += 4
   x
 
@@ -222,10 +221,11 @@ proc readIHDR(reader: var PNGReader) =
 proc readbKGD(reader: var PNGReader) =
   case reader.colorType
   of GRAYSCALE, GRAYSCALE_WITH_ALPHA:
-    discard reader.readU8() #TODO bit depth > 8
+    # We can't really use bit depth > 8
+    discard reader.readU8()
     reader.background = gray(reader.readU8())
   of TRUECOLOR, TRUECOLOR_WITH_ALPHA:
-    discard reader.readU8() #TODO bit depth > 8
+    discard reader.readU8()
     let r = reader.readU8()
     discard reader.readU8()
     let g = reader.readU8()
@@ -233,7 +233,30 @@ proc readbKGD(reader: var PNGReader) =
     let b = reader.readU8()
     reader.background = rgb(r, g, b)
   of INDEXED_COLOR:
-    discard #TODO
+    let i = int(reader.readU8())
+    if i >= reader.palette.len:
+      reader.err "invalid palette index"
+    reader.background = reader.palette[i]
+
+proc readtRNS(reader: var PNGReader) =
+  case reader.colorType
+  of GRAYSCALE, GRAYSCALE_WITH_ALPHA:
+    # We can't really use bit depth > 8
+    discard reader.readU8()
+    reader.trns = gray(reader.readU8())
+  of TRUECOLOR, TRUECOLOR_WITH_ALPHA:
+    discard reader.readU8()
+    let r = reader.readU8()
+    discard reader.readU8()
+    let g = reader.readU8()
+    discard reader.readU8()
+    let b = reader.readU8()
+    reader.trns = rgb(r, g, b)
+  of INDEXED_COLOR:
+    if reader.limit - reader.i > reader.palette.len:
+      reader.err "too many trns values"
+    for i in 0 ..< reader.palette.len:
+      reader.palette[i].a = reader.readU8()
 
 proc unfilter(reader: var PNGReader, irow: openArray[uint8], bpp: int) =
   # none, sub, up -> replace uprow directly
@@ -290,7 +313,12 @@ proc writepxs(reader: var PNGReader, crow: var openArray[RGBAColor]) =
       let b = reader.uprow[i]
       i += step
       crow[x] = rgba(r, g, b, 255u8)
-  of INDEXED_COLOR: discard #TODO
+  of INDEXED_COLOR:
+    for x in 0 ..< crow.len:
+      let i = int(reader.uprow[x])
+      if unlikely(i >= reader.palette.len):
+        reader.err "invalid palette index"
+      crow[x] = reader.palette[i]
   of GRAYSCALE_WITH_ALPHA:
     let step = int(reader.bitDepth) div 8
     var i = 0
@@ -309,11 +337,32 @@ proc writepxs(reader: var PNGReader, crow: var openArray[RGBAColor]) =
       let a = reader.uprow[(x + 3) * step]
       crow[x] = rgba(r, g, b, a)
 
+proc readPLTE(reader: var PNGReader) =
+  # For non-indexed-color, palette is just a suggestion for quantization.
+  #TODO support this in term
+  const CanHavePLTE = {TRUECOLOR, INDEXED_COLOR, TRUECOLOR_WITH_ALPHA}
+  if reader.plteseen:
+    reader.err "too many PLTE chunks"
+  if reader.colorType notin CanHavePLTE:
+    reader.err "unexpected PLTE chunk for color type"
+  let len = reader.limit - reader.i
+  if len mod 3 != 0:
+    reader.err "palette length not divisible by 3"
+  reader.palette = newSeq[RGBAColor](len)
+  for i in 0 ..< len div 3:
+    let r = reader.readU8()
+    let g = reader.readU8()
+    let b = reader.readU8()
+    reader.palette[i] = rgba(r, g, b, 255)
+  reader.plteseen = true
+
 proc readIDAT(reader: var PNGReader) =
   if reader.idatAt == reader.idatBuf.len:
     reader.err "idat buffer already filled"
   if reader.strmend:
     reader.err "stream already ended"
+  if reader.colorType == INDEXED_COLOR and not reader.plteseen:
+    reader.err "palette expected for indexed color"
   reader.strm.avail_in = cuint(reader.limit - reader.i)
   reader.strm.next_in = addr reader.iq[reader.i]
   let olen = reader.idatBuf.len - reader.idatAt
@@ -386,8 +435,6 @@ proc fromPNG*(iq: openArray[uint8]): Bitmap =
   if reader.bmp == nil: return
   if reader.width == 0 or reader.height == 0:
     reader.err "invalid zero sized png"
-  if reader.colorType == INDEXED_COLOR:
-    reader.err "indexed color not implemented yet"
   reader.initZStream()
   while reader.i < iq.len and not reader.isend:
     let len = int(reader.readPNGInt())
@@ -398,9 +445,11 @@ proc fromPNG*(iq: openArray[uint8]): Bitmap =
     reader.limit = reader.i + len
     case t
     of "IHDR": reader.err "IHDR expected to be first chunk"
+    of "PLTE": reader.readPLTE()
     of "IDAT": reader.readIDAT()
     of "IEND": reader.readIEND()
     of "bKGD": reader.readbKGD()
+    of "tRNS": reader.readtRNS()
     else: reader.readUnknown(t)
     if reader.bmp == nil: return
     let crc = crc32(0, unsafeAddr iq[j], cuint(len + 4))
diff --git a/src/io/request.nim b/src/io/request.nim
index da3437fb..4ae461df 100644
--- a/src/io/request.nim
+++ b/src/io/request.nim
@@ -317,7 +317,7 @@ proc text*(response: Response): string {.jsfunc.} =
   result = response.body.readAll()
   response.close()
 
-proc json(response: Response, ctx: JSContext): JSValue =
+proc json(response: Response, ctx: JSContext): JSValue {.jsfunc.} =
   var s = response.text()
   return JS_ParseJSON(ctx, cstring(s), cast[csize_t](s.len), cstring"<input>")
 
diff --git a/src/types/color.nim b/src/types/color.nim
index 25c93d9b..b9431340 100644
--- a/src/types/color.nim
+++ b/src/types/color.nim
@@ -212,16 +212,28 @@ const ColorsRGB* = {
 }.map((a) => (a[0], RGBColor(a[1]))).toTable()
 
 func r*(c: RGBAColor): int =
-  return int(uint32(c) shr 16 and 0xff)
+  return int((uint32(c) shr 16) and 0xff)
 
 func g*(c: RGBAColor): int =
-  return int(uint32(c) shr 8 and 0xff)
+  return int((uint32(c) shr 8) and 0xff)
 
 func b*(c: RGBAColor): int =
   return int(uint32(c) and 0xff)
 
 func a*(c: RGBAColor): int =
-  return int(uint32(c) shr 24 and 0xff)
+  return int((uint32(c) shr 24) and 0xff)
+
+proc `r=`*(c: var RGBAColor, r: uint8) =
+  c = RGBAColor(uint32(c) or (uint32(r) shl 16))
+
+proc `g=`*(c: var RGBAColor, g: uint8) =
+  c = RGBAColor(uint32(c) or (uint32(g) shl 8))
+
+proc `b=`*(c: var RGBAColor, b: uint8) =
+  c = RGBAColor(uint32(c) or uint32(b))
+
+proc `a=`*(c: var RGBAColor, a: uint8) =
+  c = RGBAColor(uint32(c) or (uint32(a) shl 24))
 
 # https://html.spec.whatwg.org/#serialisation-of-a-color
 func serialize*(color: RGBAColor): string =
diff --git a/src/utils/endians.nim b/src/utils/endians.nim
new file mode 100644
index 00000000..ea34efa7
--- /dev/null
+++ b/src/utils/endians.nim
@@ -0,0 +1,39 @@
+when defined(gcc) or defined(llvm_gcc) or defined(clang):
+  const useBuiltinSwap = true
+  proc builtin_bswap32(a: uint32): uint32 {.
+      importc: "__builtin_bswap32", nodecl, noSideEffect.}
+elif defined(icc):
+  const useBuiltinSwap = true
+  proc builtin_bswap32(a: uint32): uint32 {.
+      importc: "_bswap", nodecl, noSideEffect.}
+elif defined(vcc):
+  const useBuiltinSwap = true
+  proc builtin_bswap32(a: uint32): uint32 {.
+      importc: "_byteswap_ulong", nodecl, header: "<intrin.h>", noSideEffect.}
+else:
+  const useBuiltinSwap = false
+
+when useBuiltinSwap:
+  proc swap(u: uint32): uint32 {.inline.} =
+    return builtin_bswap32(u)
+else:
+  proc swap(u: uint32): uint32 {.inline.} =
+    return ((u and 0xFF000000) shr 24) or
+      ((u and 0x00FF0000) shr 8) or
+      ((u and 0x0000FF00) shl 8) or
+      (u shl 24)
+
+proc fromBytesBEu32*(x: openArray[uint8]): uint32 {.inline.} =
+  var u {.noInit.}: uint32
+  copyMem(addr u, unsafeAddr x[0], sizeof(uint32))
+  when system.cpuEndian == littleEndian:
+    return swap(u)
+  else:
+    return u
+
+proc toBytesBE*(u: uint32): array[sizeof(uint32), uint8] {.inline.} =
+  when system.cpuEndian == littleEndian:
+    var u = swap(u)
+    copyMem(addr result[0], addr u, sizeof(uint32))
+  else:
+    copyMem(addr result[0], unsafeAddr u, sizeof(uint32))