diff --git a/example/convert-to-nia/convert-to-nia.c b/example/convert-to-nia/convert-to-nia.c index 43a1758a..2247525f 100644 --- a/example/convert-to-nia/convert-to-nia.c +++ b/example/convert-to-nia/convert-to-nia.c @@ -159,8 +159,6 @@ static const char* g_usage = "Using -16 produces 16 bits per channel. For NIA/NIE output, this is the\n" "\"bn8\" version-and-configuration in the spec.\n" "\n" - "Combining -u and -16 is unsupported.\n" - "\n" "The -fail-if-unsandboxed flag causes the program to exit if it does not\n" "self-impose a sandbox. On Linux, it self-imposes a SECCOMP_MODE_STRICT\n" "sandbox, regardless of whether this flag was set."; @@ -318,8 +316,6 @@ parse_flags(int argc, char** argv) { if (num_one_of > 1) { return g_usage; - } else if (g_flags.output_uncompressed_png && g_flags.bit_depth_16) { - return "main: combining -u and -16 is unsupported"; } g_flags.output_nia_or_crc32_digest = (num_one_of == 0) || g_flags.output_crc32_digest; @@ -787,16 +783,17 @@ my_uncompng_write_func(void* context, bool // print_uncompressed_png_frame() { - if (g_flags.bit_depth_16) { - return false; - } uint32_t pixfmt = 0; if (g_pixfmt_is_gray) { - pixfmt = UNCOMPNG__PIXEL_FORMAT__YXXX; + pixfmt = g_flags.bit_depth_16 ? UNCOMPNG__PIXEL_FORMAT__YXXX_4X16LE + : UNCOMPNG__PIXEL_FORMAT__YXXX; } else if (wuffs_base__pixel_buffer__is_opaque(&g_pixbuf)) { - pixfmt = UNCOMPNG__PIXEL_FORMAT__BGRX; + pixfmt = g_flags.bit_depth_16 ? UNCOMPNG__PIXEL_FORMAT__BGRX_4X16LE + : UNCOMPNG__PIXEL_FORMAT__BGRX; } else { - pixfmt = UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL; + pixfmt = g_flags.bit_depth_16 + ? UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE + : UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL; } uint32_t w = wuffs_base__pixel_config__width(&g_pixbuf.pixcfg); diff --git a/lib/uncompng/uncompng.go b/lib/uncompng/uncompng.go index 1c0a821c..b3b0c745 100644 --- a/lib/uncompng/uncompng.go +++ b/lib/uncompng/uncompng.go @@ -51,25 +51,27 @@ import ( type ColorType byte const ( - // ColorTypeGray means 1 byte per pixel. + // ColorTypeGray means 1 byte per pixel (or 2 for Depth16, big-endian). // - // This matches Go's image.Gray.Pix layout. + // This matches Go's image.Gray.Pix (or Gray16, for Depth16) layout. ColorTypeGray = ColorType(1) - // ColorTypeRGBX means 4 bytes per pixel. Red, Green, Blue and the 4th - // channel is ignored. + // ColorTypeRGBX means 4 bytes per pixel (or 8 for Depth16, big-endian). + // Red, Green, Blue and the 4th channel is ignored. // - // This matches Go's image.RGBA.Pix and image.NRGBA.Pix layouts, provided - // that all of the pixels' alpha values are 0xFF. + // This matches Go's image.RGBA.Pix and image.NRGBA.Pix layouts (or RGBA64 + // or NRGBA64, for Depth16), provided that all of the pixels' alpha values + // are 0xFF (or 0xFFFF, for Depth16). ColorTypeRGBX = ColorType(2) - // ColorTypeRGBX means 4 bytes per pixel. Red, Green, Blue and Alpha. RGB - // uses non-premultiplied alpha. + // ColorTypeRGBX means 4 bytes per pixel (or 8 for Depth16, big-endian). + // Red, Green, Blue and Alpha. RGB uses non-premultiplied alpha. // - // This matches Go's image.NRGBA.Pix layout. If all of the pixels' alpha - // values are 0xFF then either ColorTypeRGBX or ColorTypeNRGBA will produce - // the same PNG output (in terms of pixels) but smaller (ColorTypeRGBX) or - // larger (ColorTypeNRGBA) output in terms of byte count. + // This matches Go's image.NRGBA.Pix layout (or NRGBA64, for Depth16). If + // all of the pixels' alpha values are 0xFF (or 0xFFFF, for Depth16) then + // either ColorTypeRGBX or ColorTypeNRGBA will produce the same PNG output + // (in terms of pixels) but smaller (ColorTypeRGBX) or larger + // (ColorTypeNRGBA) output in terms of byte count. ColorTypeNRGBA = ColorType(3) ) @@ -87,13 +89,15 @@ func (c ColorType) pngFileFormatEncoding() byte { } // Depth is the number of bits per channel. -// -// This package only supports a depth of 8. In the future, it might also -// support a depth of 16. type Depth byte const ( + // Depth8 means one byte per pixel. Depth8 = Depth(8) + + // Depth16 means two bytes per pixel. Values are big-endian like the Go + // standard library's Gray16, RGBA64 and NRGBA64 image types. + Depth16 = Depth(16) ) // Encoder is an opaque type that can convert a slice of pixel data to @@ -189,11 +193,13 @@ const ( // Encode writes the pixel data to w. It makes no allocations above whatever // w.Write makes, if any. // -// pix holds the pixel data, either 1 or 4 bytes per pixel depending on the -// colorType. width and height are measured in pixels. stride is measured in -// bytes. depth must be Depth8 although this might be relaxed in the future. +// pix holds the pixel data, either 1 or 4 bytes per pixel (doubled for +// Depth16) depending on the colorType. width and height are measured in +// pixels. stride is measured in bytes. depth must be either Depth8 or Depth16. func (e *Encoder) Encode(w io.Writer, pix []byte, width int, height int, stride int, depth Depth, colorType ColorType) error { - if (width < 0) || (height < 0) || (depth != Depth8) || (colorType.pngFileFormatEncoding() == 0xFF) { + if (width < 0) || (height < 0) || + ((depth != Depth8) && (depth != Depth16)) || + (colorType.pngFileFormatEncoding() == 0xFF) { return errors.New("uncompng: invalid argument") } else if (width > 0xFFFFFF) || (height > 0xFFFFFF) { return errors.New("uncompng: unsupported image size") @@ -214,8 +220,8 @@ func (e *Encoder) Encode(w io.Writer, pix []byte, width int, height int, stride row := pix[y*stride:] - switch colorType { - case ColorTypeGray: + switch ColorType(depth) | colorType { + case 0x08 | ColorTypeGray: row = row[:1*width] for x := 0; x < width; x++ { if (ej + 1) > ejMax { @@ -229,7 +235,7 @@ func (e *Encoder) Encode(w io.Writer, pix []byte, width int, height int, stride row = row[1:] } - case ColorTypeRGBX: + case 0x08 | ColorTypeRGBX: row = row[:4*width] for x := 0; x < width; x++ { if (ej + 3) > ejMax { @@ -245,7 +251,7 @@ func (e *Encoder) Encode(w io.Writer, pix []byte, width int, height int, stride row = row[4:] } - case ColorTypeNRGBA: + case 0x08 | ColorTypeNRGBA: row = row[:4*width] for x := 0; x < width; x++ { if (ej + 4) > ejMax { @@ -261,6 +267,61 @@ func (e *Encoder) Encode(w io.Writer, pix []byte, width int, height int, stride ej += 4 row = row[4:] } + + case 0x10 | ColorTypeGray: + row = row[:2*width] + for x := 0; x < width; x++ { + if (ej + 2) > ejMax { + if err := e.flush(w, ej, false); err != nil { + return err + } + ej = eiLater + } + e.buf[ej+0] = row[0] + e.buf[ej+1] = row[1] + ej += 2 + row = row[2:] + } + + case 0x10 | ColorTypeRGBX: + row = row[:8*width] + for x := 0; x < width; x++ { + if (ej + 6) > ejMax { + if err := e.flush(w, ej, false); err != nil { + return err + } + ej = eiLater + } + e.buf[ej+0] = row[0] + e.buf[ej+1] = row[1] + e.buf[ej+2] = row[2] + e.buf[ej+3] = row[3] + e.buf[ej+4] = row[4] + e.buf[ej+5] = row[5] + ej += 6 + row = row[8:] + } + + case 0x10 | ColorTypeNRGBA: + row = row[:8*width] + for x := 0; x < width; x++ { + if (ej + 8) > ejMax { + if err := e.flush(w, ej, false); err != nil { + return err + } + ej = eiLater + } + e.buf[ej+0] = row[0] + e.buf[ej+1] = row[1] + e.buf[ej+2] = row[2] + e.buf[ej+3] = row[3] + e.buf[ej+4] = row[4] + e.buf[ej+5] = row[5] + e.buf[ej+6] = row[6] + e.buf[ej+7] = row[7] + ej += 8 + row = row[8:] + } } } diff --git a/lib/uncompng/uncompng_test.go b/lib/uncompng/uncompng_test.go index 7723e68c..7375f33d 100644 --- a/lib/uncompng/uncompng_test.go +++ b/lib/uncompng/uncompng_test.go @@ -28,17 +28,32 @@ func encodeImage(w io.Writer, src image.Image) error { case *image.Gray: return e.Encode(w, src.Pix, b.Dx(), b.Dy(), src.Stride, Depth8, ColorTypeGray) + case *image.Gray16: + return e.Encode(w, src.Pix, b.Dx(), b.Dy(), src.Stride, Depth16, ColorTypeGray) + case *image.RGBA: if src.Opaque() { return e.Encode(w, src.Pix, b.Dx(), b.Dy(), src.Stride, Depth8, ColorTypeRGBX) } + case *image.RGBA64: + if src.Opaque() { + return e.Encode(w, src.Pix, b.Dx(), b.Dy(), src.Stride, Depth16, ColorTypeRGBX) + } + case *image.NRGBA: if src.Opaque() { return e.Encode(w, src.Pix, b.Dx(), b.Dy(), src.Stride, Depth8, ColorTypeRGBX) } else { return e.Encode(w, src.Pix, b.Dx(), b.Dy(), src.Stride, Depth8, ColorTypeNRGBA) } + + case *image.NRGBA64: + if src.Opaque() { + return e.Encode(w, src.Pix, b.Dx(), b.Dy(), src.Stride, Depth16, ColorTypeRGBX) + } else { + return e.Encode(w, src.Pix, b.Dx(), b.Dy(), src.Stride, Depth16, ColorTypeNRGBA) + } } tmp := image.NewNRGBA(b) @@ -50,16 +65,24 @@ func getPix(m image.Image) []byte { switch m := m.(type) { case *image.Gray: return m.Pix + case *image.Gray16: + return m.Pix case *image.RGBA: return m.Pix + case *image.RGBA64: + return m.Pix case *image.NRGBA: return m.Pix + case *image.NRGBA64: + return m.Pix } return nil } func TestRoundTrip(tt *testing.T) { testCases := []string{ + "36.png", + "49.png", "bricks-color.png", "bricks-gray.png", "harvesters.png", diff --git a/snippet/uncompng.c b/snippet/uncompng.c index d1750547..c6b23e55 100644 --- a/snippet/uncompng.c +++ b/snippet/uncompng.c @@ -36,16 +36,24 @@ #include #include +// clang-format off + // UNCOMPNG__PIXEL_FORMAT__ETC are the valid pixel_format values to pass to // uncompng__encode. // // These constants' values are the same as the corresponding Wuffs definitions, // after replacing the name's "WUFFS_BASE" prefix with "UNCOMPNG". This file is // stand-alone. It does not #include any Wuffs code. -#define UNCOMPNG__PIXEL_FORMAT__Y 0x20000008 -#define UNCOMPNG__PIXEL_FORMAT__YXXX 0x30008888 -#define UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL 0x81008888 -#define UNCOMPNG__PIXEL_FORMAT__BGRX 0x90008888 +#define UNCOMPNG__PIXEL_FORMAT__Y 0x20000008 +#define UNCOMPNG__PIXEL_FORMAT__Y_16LE 0x2000000B +#define UNCOMPNG__PIXEL_FORMAT__YXXX 0x30008888 +#define UNCOMPNG__PIXEL_FORMAT__YXXX_4X16LE 0x3000BBBB +#define UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL 0x81008888 +#define UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE 0x8100BBBB +#define UNCOMPNG__PIXEL_FORMAT__BGRX 0x90008888 +#define UNCOMPNG__PIXEL_FORMAT__BGRX_4X16LE 0x9000BBBB + +// clang-format on // UNCOMPNG__RESULT__ETC can be returned by uncompng__encode. write_func can // also return its own negative error codes, which are passed on. @@ -185,23 +193,40 @@ uncompng__private_impl_initialize_buffer(uint32_t width, uncompng__private_impl_buffer[0x0015] = (uint8_t)(height >> 16); uncompng__private_impl_buffer[0x0016] = (uint8_t)(height >> 8); uncompng__private_impl_buffer[0x0017] = (uint8_t)(height >> 0); - uncompng__private_impl_buffer[0x0018] = 8; + uint8_t depth; uint8_t color_type; switch (pixel_format) { case UNCOMPNG__PIXEL_FORMAT__Y: case UNCOMPNG__PIXEL_FORMAT__YXXX: + depth = 8; color_type = 0; break; case UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL: + depth = 8; color_type = 6; break; case UNCOMPNG__PIXEL_FORMAT__BGRX: + depth = 8; + color_type = 2; + break; + case UNCOMPNG__PIXEL_FORMAT__Y_16LE: + case UNCOMPNG__PIXEL_FORMAT__YXXX_4X16LE: + depth = 16; + color_type = 0; + break; + case UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + depth = 16; + color_type = 6; + break; + case UNCOMPNG__PIXEL_FORMAT__BGRX_4X16LE: + depth = 16; color_type = 2; break; default: return; } + uncompng__private_impl_buffer[0x0018] = depth; uncompng__private_impl_buffer[0x0019] = color_type; uncompng__private_impl_buffer[0x001A] = 0; uncompng__private_impl_buffer[0x001B] = 0; @@ -413,6 +438,22 @@ uncompng__private_impl_do_encode(int (*write_func)(void* context, } break; + case UNCOMPNG__PIXEL_FORMAT__Y_16LE: + for (uint32_t x = 0; x < width; x++) { + if ((ej + 2) > ej_max) { + int err = + uncompng__private_impl_flush(write_func, context, ej, false); + if (err != 0) { + return err; + } + ej = ei_later; + } + uncompng__private_impl_buffer[ej++] = row[1]; + uncompng__private_impl_buffer[ej++] = row[0]; + row += 2; + } + break; + case UNCOMPNG__PIXEL_FORMAT__YXXX: for (uint32_t x = 0; x < width; x++) { if ((ej + 1) > ej_max) { @@ -428,6 +469,22 @@ uncompng__private_impl_do_encode(int (*write_func)(void* context, } break; + case UNCOMPNG__PIXEL_FORMAT__YXXX_4X16LE: + for (uint32_t x = 0; x < width; x++) { + if ((ej + 2) > ej_max) { + int err = + uncompng__private_impl_flush(write_func, context, ej, false); + if (err != 0) { + return err; + } + ej = ei_later; + } + uncompng__private_impl_buffer[ej++] = row[1]; + uncompng__private_impl_buffer[ej++] = row[0]; + row += 8; + } + break; + case UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL: for (uint32_t x = 0; x < width; x++) { if ((ej + 4) > ej_max) { @@ -446,6 +503,28 @@ uncompng__private_impl_do_encode(int (*write_func)(void* context, } break; + case UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + for (uint32_t x = 0; x < width; x++) { + if ((ej + 8) > ej_max) { + int err = + uncompng__private_impl_flush(write_func, context, ej, false); + if (err != 0) { + return err; + } + ej = ei_later; + } + uncompng__private_impl_buffer[ej++] = row[5]; + uncompng__private_impl_buffer[ej++] = row[4]; + uncompng__private_impl_buffer[ej++] = row[3]; + uncompng__private_impl_buffer[ej++] = row[2]; + uncompng__private_impl_buffer[ej++] = row[1]; + uncompng__private_impl_buffer[ej++] = row[0]; + uncompng__private_impl_buffer[ej++] = row[7]; + uncompng__private_impl_buffer[ej++] = row[6]; + row += 8; + } + break; + case UNCOMPNG__PIXEL_FORMAT__BGRX: for (uint32_t x = 0; x < width; x++) { if ((ej + 3) > ej_max) { @@ -463,6 +542,26 @@ uncompng__private_impl_do_encode(int (*write_func)(void* context, } break; + case UNCOMPNG__PIXEL_FORMAT__BGRX_4X16LE: + for (uint32_t x = 0; x < width; x++) { + if ((ej + 6) > ej_max) { + int err = + uncompng__private_impl_flush(write_func, context, ej, false); + if (err != 0) { + return err; + } + ej = ei_later; + } + uncompng__private_impl_buffer[ej++] = row[5]; + uncompng__private_impl_buffer[ej++] = row[4]; + uncompng__private_impl_buffer[ej++] = row[3]; + uncompng__private_impl_buffer[ej++] = row[2]; + uncompng__private_impl_buffer[ej++] = row[1]; + uncompng__private_impl_buffer[ej++] = row[0]; + row += 8; + } + break; + default: return UNCOMPNG__RESULT__INVALID_ARGUMENT; } @@ -490,11 +589,19 @@ uncompng__encode(int (*write_func)(void* context, case UNCOMPNG__PIXEL_FORMAT__Y: bytes_per_pixel = 1u; break; + case UNCOMPNG__PIXEL_FORMAT__Y_16LE: + bytes_per_pixel = 2u; + break; case UNCOMPNG__PIXEL_FORMAT__YXXX: case UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL: case UNCOMPNG__PIXEL_FORMAT__BGRX: bytes_per_pixel = 4u; break; + case UNCOMPNG__PIXEL_FORMAT__YXXX_4X16LE: + case UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + case UNCOMPNG__PIXEL_FORMAT__BGRX_4X16LE: + bytes_per_pixel = 8u; + break; default: return UNCOMPNG__RESULT__INVALID_ARGUMENT; } diff --git a/test/data/36.png b/test/data/36.png new file mode 100644 index 00000000..27655d11 Binary files /dev/null and b/test/data/36.png differ diff --git a/test/data/49.png b/test/data/49.png new file mode 100644 index 00000000..7681b0a2 Binary files /dev/null and b/test/data/49.png differ diff --git a/test/data/README.md b/test/data/README.md index 284a8bdf..4c7173b1 100644 --- a/test/data/README.md +++ b/test/data/README.md @@ -38,6 +38,9 @@ The non-ascii directory holds trivial UTF-8 (but not ASCII) text files. --- +`36.png` and `49.png` are simple, artificially generated images. The generation +script is `gen-36-49.go` from https://github.com/nigeltao/etc2 + `DCI-P3-D65.icc` comes from [color.org](https://www.color.org/chardata/rgb/DCIP3.xalter). `DCI-P3-D65.icc.zlib` is a zlib-compresion of that, created by Go's standard diff --git a/test/nia-checksums-of-data.txt b/test/nia-checksums-of-data.txt index 196d36b4..7429c4e4 100644 --- a/test/nia-checksums-of-data.txt +++ b/test/nia-checksums-of-data.txt @@ -1,4 +1,6 @@ # Generated by script/print-nia-checksums.sh +OK. 9720c028 test/data/36.png +OK. f2a0a3f6 test/data/49.png OK. d3bb0beb test/data/DCI-P3-D65.icc OK. 646e081a test/data/animated-red-blue.000000.nie OK. 181c6916 test/data/animated-red-blue.000001.nie