blob: 2b3396b449088fe289ec3042079ffc6200f66728 [file] [log] [blame]
// Copyright 2020 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------
// Uncomment one of these #define lines to test and bench alternative mimic
// libraries (libspng, lodepng or stb_image) instead of libpng.
//
// These are collectively referred to as
// WUFFS_MIMICLIB_USE_XXX_INSTEAD_OF_LIBPNG.
//
// #define WUFFS_MIMICLIB_USE_LIBSPNG_INSTEAD_OF_LIBPNG 1
// #define WUFFS_MIMICLIB_USE_LODEPNG_INSTEAD_OF_LIBPNG 1
// #define WUFFS_MIMICLIB_USE_STB_IMAGE_INSTEAD_OF_LIBPNG 1
// -------------------------------- WUFFS_MIMICLIB_USE_XXX_INSTEAD_OF_LIBPNG
#if defined(WUFFS_MIMICLIB_USE_LIBSPNG_INSTEAD_OF_LIBPNG)
// We #include a foo.c file, not a foo.h file, as libspng is a "single file C
// library".
#include "/path/to/your/copy/of/github.com/randy408/libspng/spng/spng.c"
// We deliberately do not define the
// WUFFS_MIMICLIB_PNG_DOES_NOT_SUPPORT_QUIRK_IGNORE_CHECKSUM macro.
// We deliberately do not define the
// WUFFS_MIMICLIB_PNG_DOES_NOT_VERIFY_CHECKSUM macro.
// libspng (version 0.6.2, released November 2020) calculates but does not
// verify the CRC-32 checksum on the final IDAT chunk. It also does not verify
// the Adler-32 checksum. After calling spng_decode_image, it ends in
// SPNG_STATE_EOI but not a later state such as SPNG_STATE_AFTER_IDAT.
//
// libspng commit 9c35dc3 "fix handling of SPNG_CTX_IGNORE_ADLER32", submitted
// December 2020, fixed Adler-32 but not CRC-32.
#define WUFFS_MIMICLIB_PNG_DOES_NOT_VERIFY_FINAL_IDAT_CHECKSUMS 1
const char* //
mimic_png_decode(uint64_t* n_bytes_out,
wuffs_base__io_buffer* dst,
uint32_t wuffs_initialize_flags,
wuffs_base__pixel_format pixfmt,
uint32_t* quirks_ptr,
size_t quirks_len,
wuffs_base__io_buffer* src) {
wuffs_base__io_buffer dst_fallback =
wuffs_base__slice_u8__writer(g_mimiclib_scratch_slice_u8);
if (!dst) {
dst = &dst_fallback;
}
const char* ret = NULL;
spng_ctx* ctx = spng_ctx_new(0);
size_t i;
for (i = 0; i < quirks_len; i++) {
uint32_t q = quirks_ptr[i];
if (q == WUFFS_BASE__QUIRK_IGNORE_CHECKSUM) {
spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE);
}
}
if (spng_set_png_buffer(ctx, wuffs_base__io_buffer__reader_pointer(src),
wuffs_base__io_buffer__reader_length(src))) {
ret = "mimic_png_decode: spng_set_png_buffer failed";
goto cleanup0;
}
int fmt = 0;
bool swap_bgra_rgba = false;
switch (pixfmt.repr) {
case WUFFS_BASE__PIXEL_FORMAT__Y:
fmt = SPNG_FMT_G8;
break;
case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL:
// libspng doesn't do BGRA8. RGBA8 is the closest approximation. We'll
// fix it up later.
fmt = SPNG_FMT_RGBA8;
swap_bgra_rgba = true;
break;
default:
ret = "mimic_png_decode: unsupported pixfmt";
goto cleanup0;
}
size_t n = 0;
if (spng_decoded_image_size(ctx, fmt, &n)) {
ret = "mimic_png_decode: spng_decoded_image_size failed";
goto cleanup0;
}
if (n > wuffs_base__io_buffer__writer_length(dst)) {
ret = "mimic_png_decode: image is too large";
goto cleanup0;
}
uint8_t* dst_ptr = wuffs_base__io_buffer__writer_pointer(dst);
if (spng_decode_image(ctx, dst_ptr, n, fmt, 0)) {
ret = "mimic_png_decode: spng_decode_image failed";
goto cleanup0;
}
dst->meta.wi += n;
if (n_bytes_out) {
*n_bytes_out += n;
}
// Fix up BGRA8 vs RGBA8.
if (swap_bgra_rgba) {
for (; n >= 4; n -= 4) {
uint8_t swap = dst_ptr[0];
dst_ptr[0] = dst_ptr[2];
dst_ptr[2] = swap;
dst_ptr += 4;
}
}
cleanup0:;
spng_ctx_free(ctx);
return ret;
}
// -------------------------------- WUFFS_MIMICLIB_USE_XXX_INSTEAD_OF_LIBPNG
#elif defined(WUFFS_MIMICLIB_USE_LODEPNG_INSTEAD_OF_LIBPNG)
// We #include a foo.cpp file, not a foo.h file, as lodepng is a "single file
// C++ library".
#include "/path/to/your/copy/of/github.com/lvandeve/lodepng/lodepng.cpp"
#define WUFFS_MIMICLIB_PNG_DOES_NOT_SUPPORT_QUIRK_IGNORE_CHECKSUM 1
// We deliberately do not define the
// WUFFS_MIMICLIB_PNG_DOES_NOT_VERIFY_CHECKSUM macro.
// We deliberately do not define the
// WUFFS_MIMICLIB_PNG_DOES_NOT_VERIFY_FINAL_IDAT_CHECKSUMS macro.
const char* //
mimic_png_decode(uint64_t* n_bytes_out,
wuffs_base__io_buffer* dst,
uint32_t wuffs_initialize_flags,
wuffs_base__pixel_format pixfmt,
uint32_t* quirks_ptr,
size_t quirks_len,
wuffs_base__io_buffer* src) {
wuffs_base__io_buffer dst_fallback =
wuffs_base__slice_u8__writer(g_mimiclib_scratch_slice_u8);
if (!dst) {
dst = &dst_fallback;
}
uint64_t n = 0;
LodePNGColorType color_type = 0;
unsigned int bitdepth = 8;
bool swap_bgra_rgba = false;
switch (pixfmt.repr) {
case WUFFS_BASE__PIXEL_FORMAT__Y:
n = 1;
color_type = LCT_GREY;
break;
case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL:
n = 4;
// lodepng doesn't do BGRA8. RGBA8 is the closest approximation. We'll
// fix it up later.
color_type = LCT_RGBA;
swap_bgra_rgba = true;
break;
default:
return "mimic_png_decode: unsupported pixfmt";
}
unsigned char* output = 0;
unsigned int width = 0;
unsigned int height = 0;
unsigned int err =
lodepng_decode_memory(&output, &width, &height, //
wuffs_base__io_buffer__reader_pointer(src), //
wuffs_base__io_buffer__reader_length(src), //
color_type, bitdepth);
if (err) {
return lodepng_error_text(err);
}
const char* ret = NULL;
if ((width > 0xFFFF) || (height > 0xFFFF)) {
ret = "mimic_png_decode: image is too large";
goto cleanup0;
}
n *= ((uint64_t)width) * ((uint64_t)height);
if (n > wuffs_base__io_buffer__writer_length(dst)) {
ret = "mimic_png_decode: image is too large";
goto cleanup0;
}
// Copy from the mimic library's output buffer to Wuffs' dst buffer.
uint8_t* dst_ptr = wuffs_base__io_buffer__writer_pointer(dst);
memcpy(dst_ptr, output, n);
dst->meta.wi += n;
if (n_bytes_out) {
*n_bytes_out += n;
}
// Fix up BGRA8 vs RGBA8.
if (swap_bgra_rgba) {
for (; n >= 4; n -= 4) {
uint8_t swap = dst_ptr[0];
dst_ptr[0] = dst_ptr[2];
dst_ptr[2] = swap;
dst_ptr += 4;
}
}
cleanup0:;
free(output);
return ret;
}
// -------------------------------- WUFFS_MIMICLIB_USE_XXX_INSTEAD_OF_LIBPNG
#elif defined(WUFFS_MIMICLIB_USE_STB_IMAGE_INSTEAD_OF_LIBPNG)
// We #include a foo.cpp file, not a foo.h file, as stb_image is a "single file
// C library".
#define STB_IMAGE_IMPLEMENTATION
#include "/path/to/your/copy/of/github.com/nothings/stb/stb_image.h"
// We deliberately do not define the
// WUFFS_MIMICLIB_PNG_DOES_NOT_SUPPORT_QUIRK_IGNORE_CHECKSUM macro. The
// stb_image library always ignores checksums.
#define WUFFS_MIMICLIB_PNG_DOES_NOT_VERIFY_CHECKSUM 1
// We deliberately do not define the
// WUFFS_MIMICLIB_PNG_DOES_NOT_VERIFY_FINAL_IDAT_CHECKSUMS macro.
const char* //
mimic_png_decode(uint64_t* n_bytes_out,
wuffs_base__io_buffer* dst,
uint32_t wuffs_initialize_flags,
wuffs_base__pixel_format pixfmt,
uint32_t* quirks_ptr,
size_t quirks_len,
wuffs_base__io_buffer* src) {
wuffs_base__io_buffer dst_fallback =
wuffs_base__slice_u8__writer(g_mimiclib_scratch_slice_u8);
if (!dst) {
dst = &dst_fallback;
}
uint64_t n = 0;
bool swap_bgra_rgba = false;
switch (pixfmt.repr) {
case WUFFS_BASE__PIXEL_FORMAT__Y:
n = 1;
break;
case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL:
n = 4;
// stb_image doesn't do BGRA8. RGBA8 is the closest approximation. We'll
// fix it up later.
swap_bgra_rgba = true;
break;
default:
return "mimic_png_decode: unsupported pixfmt";
}
int width = 0;
int height = 0;
int channels_in_file = 0;
unsigned char* output =
stbi_load_from_memory(wuffs_base__io_buffer__reader_pointer(src), //
wuffs_base__io_buffer__reader_length(src), //
&width, &height, &channels_in_file, n);
if (!output) {
return "mimic_png_decode: could not load image";
}
const char* ret = NULL;
if ((width > 0xFFFF) || (height > 0xFFFF)) {
ret = "mimic_png_decode: image is too large";
goto cleanup0;
}
n *= ((uint64_t)width) * ((uint64_t)height);
if (n > wuffs_base__io_buffer__writer_length(dst)) {
ret = "mimic_png_decode: image is too large";
goto cleanup0;
}
// Copy from the mimic library's output buffer to Wuffs' dst buffer.
uint8_t* dst_ptr = wuffs_base__io_buffer__writer_pointer(dst);
memcpy(dst_ptr, output, n);
dst->meta.wi += n;
if (n_bytes_out) {
*n_bytes_out += n;
}
// Fix up BGRA8 vs RGBA8.
if (swap_bgra_rgba) {
for (; n >= 4; n -= 4) {
uint8_t swap = dst_ptr[0];
dst_ptr[0] = dst_ptr[2];
dst_ptr[2] = swap;
dst_ptr += 4;
}
}
cleanup0:;
stbi_image_free(output);
return ret;
}
// -------------------------------- WUFFS_MIMICLIB_USE_XXX_INSTEAD_OF_LIBPNG
#else
#include "png.h"
#define WUFFS_MIMICLIB_PNG_DOES_NOT_SUPPORT_QUIRK_IGNORE_CHECKSUM 1
// We deliberately do not define the
// WUFFS_MIMICLIB_PNG_DOES_NOT_VERIFY_CHECKSUM macro.
// We deliberately do not define the
// WUFFS_MIMICLIB_PNG_DOES_NOT_VERIFY_FINAL_IDAT_CHECKSUMS macro.
const char* //
mimic_png_decode(uint64_t* n_bytes_out,
wuffs_base__io_buffer* dst,
uint32_t wuffs_initialize_flags,
wuffs_base__pixel_format pixfmt,
uint32_t* quirks_ptr,
size_t quirks_len,
wuffs_base__io_buffer* src) {
wuffs_base__io_buffer dst_fallback =
wuffs_base__slice_u8__writer(g_mimiclib_scratch_slice_u8);
if (!dst) {
dst = &dst_fallback;
}
const char* ret = NULL;
png_image pi;
memset(&pi, 0, (sizeof pi));
pi.version = PNG_IMAGE_VERSION;
if (!png_image_begin_read_from_memory(&pi, src->data.ptr + src->meta.ri,
src->meta.wi - src->meta.ri)) {
ret = "mimic_png_decode: png_image_begin_read_from_memory failed";
goto cleanup0;
}
size_t i;
for (i = 0; i < quirks_len; i++) {
uint32_t q = quirks_ptr[i];
if (q == WUFFS_BASE__QUIRK_IGNORE_CHECKSUM) {
// TODO: configure libpng to ignore Adler-32 and CRC-32 checksums.
//
// The libpng "simplified API" (based on the png_image struct type) does
// not offer an equivalent of libpng's lower level API's
// png_set_crc_action and png_set_option(etc, PNG_IGNORE_ADLER32, etc).
// But the lower level API is complicated (hence why libpng introduced a
// "simplified API" in the first place), especially trying to make the
// equivalent of the png_image.format field work.
//
// Instead, we simply disable (via the
// WUFFS_MIMICLIB_PNG_DOES_NOT_SUPPORT_QUIRK_IGNORE_CHECKSUM macro) any
// tests or benches that try to use WUFFS_BASE__QUIRK_IGNORE_CHECKSUM.
}
}
switch (pixfmt.repr) {
case WUFFS_BASE__PIXEL_FORMAT__Y:
pi.format = PNG_FORMAT_GRAY;
break;
case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL:
pi.format = PNG_FORMAT_BGRA;
break;
default:
ret = "mimic_png_decode: unsupported pixfmt";
goto cleanup0;
}
size_t n = PNG_IMAGE_SIZE(pi);
if (n > wuffs_base__io_buffer__writer_length(dst)) {
ret = "mimic_png_decode: image is too large";
goto cleanup0;
}
if (!png_image_finish_read(
&pi, NULL, wuffs_base__io_buffer__writer_pointer(dst), 0, NULL)) {
ret = "mimic_png_decode: png_image_finish_read failed";
goto cleanup0;
}
dst->meta.wi += n;
if (n_bytes_out) {
*n_bytes_out += n;
}
cleanup0:;
png_image_free(&pi);
return ret;
}
#endif
// -------------------------------- WUFFS_MIMICLIB_USE_XXX_INSTEAD_OF_LIBPNG