diff --git a/src/memio.c b/src/memio.c new file mode 100644 index 000000000..175b346be --- /dev/null +++ b/src/memio.c @@ -0,0 +1,278 @@ +/*====================================================================* +- Copyright (C) 2017 Milner Technologies, Inc. This content is a +- component of Leptonica and is provided under the terms of the +- leptonica license. +- +- Redistribution and use in source and binary forms, with or without +- modification, are permitted provided that the following conditions +- are met: +- 1. Redistributions of source code must retain the above copyright +- notice, this list of conditions and the following disclaimer. +- 2. Redistributions in binary form must reproduce the Leptonica +- copyright notice, this list of conditions and the following +- disclaimer in the documentation and/or other materials +- provided with the distribution. +- +- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY +- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +- OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*====================================================================*/ + +/*! +* \file memio.c +*
+* libpng read/write callback replacements for performing memory I/O. +*+ +*/#ifdef HAVE_CONFIG_H` +#include "config_auto.h" +#endif /* HAVE_CONFIG_H */ + +#include
Buffer allocation size. Set this small enough to avoid consuming unnecessary memory, +* but larger than an average image.+*/ +#define BUFFER_SIZE 8192 + +/* +*
+* libpng callback for writing an image into a linked list of memory buffers. +*+*/ +void +memio_png_write_data(png_structp png_ptr, png_bytep data, png_size_t len) +{ + MEMIODATA* thing; + MEMIODATA* last; + int written = 0; + + PROCNAME("memio_png_write_data"); + + thing = (struct MemIOData*)png_get_io_ptr(png_ptr); + last = (struct MemIOData*)thing->m_Last; + if (last->m_Buffer == 0) + { + if (len > BUFFER_SIZE) + { +#ifdef __cplusplus + last->m_Buffer = new char[len]; +#else + last->m_Buffer = LEPT_MALLOC(len); +#endif /* __cplusplus */ + memcpy(last->m_Buffer, data, len); + last->m_Size = last->m_Count = len; + return; + } + +#ifdef __cplusplus + last->m_Buffer = new char[BUFFER_SIZE]; +#else + last->m_Buffer = LEPT_MALLOC(BUFFER_SIZE); +#endif /* __cplusplus */ + last->m_Size = BUFFER_SIZE; + } + + while (written < len) + { + if (last->m_Count == last->m_Size) + { +#ifdef __cplusplus + MEMIODATA* next = new MEMIODATA; +#else + MEMIODATA* next = LEPT_MALLOC(sizeof(MEMIODATA)); +#endif /* __cplusplus */ + next->m_Next = 0; + next->m_Count = 0; + next->m_Last = next; + + last->m_Next = next; + last = thing->m_Last = next; + +#ifdef __cplusplus + last->m_Buffer = new char[BUFFER_SIZE]; +#else + last->m_Buffer = LEPT_MALLOC(BUFFER_SIZE); +#endif /* __cplusplus */ + last->m_Size = BUFFER_SIZE; + } + + /* following paragraph localizes vars only. */ + { + int remainingSpace = last->m_Size - last->m_Count; + int remainingToWrite = len - written; + if (remainingSpace < remainingToWrite) + { + memcpy(last->m_Buffer + last->m_Count, data + written, remainingSpace); + written += remainingSpace; + + last->m_Count += remainingSpace; + } + else + { + memcpy(last->m_Buffer + last->m_Count, data + written, remainingToWrite); + written += remainingToWrite; + last->m_Count += remainingToWrite; + } + } + } +} + + +/* +*
+* Consolidate write buffers into a single buffer in the head of the linked list. +*+*/ +void +memio_png_flush(MEMIODATA* pthing) +{ + int amount = 0; + MEMIODATA* buffer = 0; + int copied = 0; + char* data = 0; + + PROCNAME("memio_png_write_data"); + + /* if the data was contained in one buffer just give the buffer to the user. */ + if (pthing->m_Next == 0) + { + return; + } + + /* consolidate multiple buffers into one new one. */ + + /* add the buffer sizes together. */ + amount = pthing->m_Count; + buffer = pthing->m_Next; + while (buffer != 0) + { + amount += buffer->m_Count; + buffer = buffer->m_Next; + } + + /* copy data to a new buffer. */ +#ifdef __cplusplus + data = new char[amount]; +#else + data = LEPT_MALLOC(amount); +#endif /* __cplusplus */ + memcpy(data, pthing->m_Buffer, pthing->m_Count); + copied = pthing->m_Count; + +#ifdef __cplusplus + delete[] pthing->m_Buffer; +#else + LEPT_FREE(pthing->m_Buffer); +#endif /* __cplusplus */ + pthing->m_Buffer = 0; + /* don't delete original "thing" because we don't control it. */ + + buffer = pthing->m_Next; + pthing->m_Next = 0; + while (buffer != 0 && copied < amount) + { + MEMIODATA* old; + memcpy(data + copied, buffer->m_Buffer, buffer->m_Count); + copied += buffer->m_Count; + + old = buffer; + buffer = buffer->m_Next; + +#ifdef __cplusplus + delete[] old->m_Buffer; + delete old; +#else + LEPT_FREE(old->m_Buffer); + LEPT_FREE(old); +#endif /* __cplusplus */ + } + + /* + assert(copied == amount); + */ + + pthing->m_Buffer = data; + pthing->m_Count = copied; + pthing->m_Size = amount; + return; +} + + +/* +*
+* libpng callback that reads an image from a single memory buffer. +*+*/ +void +memio_png_read_data(png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) +{ + PROCNAME("memio_png_read_data"); + + MEMIODATA* thing = (MEMIODATA*)png_get_io_ptr(png_ptr); + + memcpy(outBytes, thing->m_Buffer + thing->m_Count, byteCountToRead); + thing->m_Count += byteCountToRead; +} + +void +memio_free(MEMIODATA* pthing) +{ + MEMIODATA* buffer; + + PROCNAME("memio_free"); + + if (pthing->m_Buffer != 0) + { +#ifdef __cplusplus + delete[] pthing->m_Buffer; +#else + LEPT_FREE(pthing->m_Buffer); +#endif /* __cplusplus */ + } + + pthing->m_Buffer = 0; + buffer = pthing->m_Next; + while (buffer != 0) + { + MEMIODATA* old = buffer; + buffer = buffer->m_Next; + +#ifdef __cplusplus + if (old->m_Buffer != 0) + { + delete[] old->m_Buffer; + } + + delete old; +#else + if (old->m_Buffer != 0) + { + LEPT_FREE(old->m_Buffer); + } + + LEPT_FREE(old); +#endif /* __cplusplus */ + } +} + +/* --------------------------------------------*/ +#endif /* HAVE_LIBPNG */ +/* --------------------------------------------*/ diff --git a/src/memio.h b/src/memio.h new file mode 100644 index 000000000..02ed5b2b2 --- /dev/null +++ b/src/memio.h @@ -0,0 +1,101 @@ +/*====================================================================* +- Copyright (C) 2017 Milner Technologies, Inc. This content is a +- component of leptonica and is provided under the terms of the +- Leptonica license. +- +- Redistribution and use in source and binary forms, with or without +- modification, are permitted provided that the following conditions +- are met: +- 1. Redistributions of source code must retain the above copyright +- notice, this list of conditions and the following disclaimer. +- 2. Redistributions in binary form must reproduce the Leptonica +- copyright notice, this list of conditions and the following +- disclaimer in the documentation and/or other materials +- provided with the distribution. +- +- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY +- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +- OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*====================================================================*/ + +/*! +* \file memio.h +*
+* libpng read/write callback replacements for performing memory I/O. +*+*/ + +#ifndef LEPTONICA_MEMIO_H +#define LEPTONICA_MEMIO_H + +/* --------------------------------------------*/ +#if HAVE_LIBPNG /* defined in environ.h */ +/* --------------------------------------------*/ + +#include
+* A node in a linked list of memory buffers that hold I/O content. +*+*/ +struct MemIOData +{ + /* + *
Pointer to this node's I/O content.+ */ + char* m_Buffer; + + /* + *
The number of I/O content bytes read or written to m_Buffer.+ */ + int m_Count; + + /* + *
The allocated size of m_Buffer.+ */ + int m_Size; + + /* + *
Pointer to the next node in the list. Zero if this is the last node.+ */ + struct MemIOData* m_Next; + + /* + *
Pointer to the last node in the linked list. The last node is where new + * content is written.+ */ + struct MemIOData* m_Last; +}; + +typedef struct MemIOData MEMIODATA; + +#ifndef NO_PROTOS + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void memio_png_write_data(png_structp png_ptr, png_bytep data, png_size_t length); +void memio_png_flush(MEMIODATA* pthing); +void memio_png_read_data(png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead); +void memio_free(MEMIODATA* pthing); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* NO_PROTOS */ + +/* --------------------------------------------*/ +#endif /* HAVE_LIBPNG */ +/* --------------------------------------------*/ + +#endif diff --git a/src/pngio.c b/src/pngio.c index c4742cd3f..05dee2253 100644 --- a/src/pngio.c +++ b/src/pngio.c @@ -113,6 +113,7 @@ #if HAVE_LIBPNG /* defined in environ.h */ /* --------------------------------------------*/ +#include "memio.h" #include "png.h" #if HAVE_LIBZ @@ -121,6 +122,10 @@ #define Z_DEFAULT_COMPRESSION (-1) #endif /* HAVE_LIBZ */ +#ifndef Z_DEFAULT_COMPRESSION +#define Z_DEFAULT_COMPRESSION (-1) +#endif + /* ------------------ Set default for read option -------------------- */ /* Strip 16 bpp --> 8 bpp on reading png; default is for stripping. * If you don't strip, you can't read the gray-alpha spp = 2 images. */ @@ -475,6 +480,365 @@ PIXCMAP *cmap; png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return pix; } +/*! +* \brief pixReadStreamPng() +* +* \param[in] fp file stream +* \return pix, or NULL on error +* +*
+* Notes: +* (1) If called from pixReadStream(), the stream is positioned +* at the beginning of the file. +* (2) To do sequential reads of png format images from a stream, +* use pixReadStreamPng() +* (3) Any image with alpha is converted to RGBA (spp = 4, with +* equal red, green and blue channels) on reading. +* There are three important cases with alpha: +* (a) grayscale-with-alpha (spp = 2), where bpp = 8, and each +* pixel has an associated alpha (transparency) value +* in the second component of the image data. +* (b) spp = 1, d = 1 with colormap and alpha in the trans array. +* Transparency is usually associated with the white background. +* (c) spp = 1, d = 8 with colormap and alpha in the trans array. +* Each color in the colormap has a separate transparency value. +* (4) We use the high level png interface, where the transforms are set +* up in advance and the header and image are read with a single +* call. The more complicated interface, where the header is +* read first and the buffers for the raster image are user- +* allocated before reading the image, works for single images, +* but I could not get it to work properly for the successive +* png reads that are required by pixaReadStream(). +*+*/ +PIX * +pixReadMemoryPng(const l_uint8* user_data, + size_t user_data_size) +{ + l_uint8 byte; + l_int32 rval, gval, bval; + l_int32 i, j, k, index, ncolors, bitval; + l_int32 wpl, d, spp, cindex, tRNS; + l_uint32 png_transforms; + l_uint32 *data, *line, *ppixel; + int num_palette, num_text, num_trans; + png_byte bit_depth, color_type, channels; + png_uint_32 w, h, rowbytes; + png_uint_32 xres, yres; + png_bytep rowptr, trans; + png_bytep *row_pointers; + png_structp png_ptr; + png_infop info_ptr, end_info; + png_colorp palette; + png_textp text_ptr; /* ptr to text_chunk */ + PIX *pix, *pixt; + PIXCMAP *cmap; + MEMIODATA state; + + PROCNAME("pixReadMemoryPng"); + + if (!user_data) + return (PIX *)ERROR_PTR("user_data not defined", procName, NULL); + if (user_data_size < 1) + return (PIX *)ERROR_PTR("user_data_size not defined", procName, NULL); + + state.m_Next = 0; + state.m_Count = 0; + state.m_Last = &state; + state.m_Buffer = (char*)user_data; + state.m_Size = user_data_size; + pix = NULL; + + /* Allocate the 3 data structures */ + if ((png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, + (png_voidp)NULL, NULL, NULL)) == NULL) + return (PIX *)ERROR_PTR("png_ptr not made", procName, NULL); + + if ((info_ptr = png_create_info_struct(png_ptr)) == NULL) { + png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); + return (PIX *)ERROR_PTR("info_ptr not made", procName, NULL); + } + + if ((end_info = png_create_info_struct(png_ptr)) == NULL) { + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + return (PIX *)ERROR_PTR("end_info not made", procName, NULL); + } + + /* Set up png setjmp error handling */ + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + return (PIX *)ERROR_PTR("internal png error", procName, NULL); + } + + png_set_read_fn(png_ptr, &state, memio_png_read_data); + + /* ---------------------------------------------------------- * + * Set the transforms flags. Whatever happens here, + * NEVER invert 1 bpp using PNG_TRANSFORM_INVERT_MONO. + * Also, do not use PNG_TRANSFORM_EXPAND, which would + * expand all images with bpp < 8 to 8 bpp. + * ---------------------------------------------------------- */ + /* To strip 16 --> 8 bit depth, use PNG_TRANSFORM_STRIP_16 */ + if (var_PNG_STRIP_16_TO_8 == 1) { /* our default */ + png_transforms = PNG_TRANSFORM_STRIP_16; + } + else { + png_transforms = PNG_TRANSFORM_IDENTITY; + L_INFO("not stripping 16 --> 8 in png reading\n", procName); + } + + /* Read it */ + png_read_png(png_ptr, info_ptr, png_transforms, NULL); + + row_pointers = png_get_rows(png_ptr, info_ptr); + w = png_get_image_width(png_ptr, info_ptr); + h = png_get_image_height(png_ptr, info_ptr); + bit_depth = png_get_bit_depth(png_ptr, info_ptr); + rowbytes = png_get_rowbytes(png_ptr, info_ptr); + color_type = png_get_color_type(png_ptr, info_ptr); + channels = png_get_channels(png_ptr, info_ptr); + spp = channels; + tRNS = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ? 1 : 0; + + if (spp == 1) { + d = bit_depth; + } + else { /* spp == 2 (gray + alpha), spp == 3 (rgb), spp == 4 (rgba) */ + d = 4 * bit_depth; + } + + /* Remove if/when this is implemented for all bit_depths */ + if (spp == 3 && bit_depth != 8) { + fprintf(stderr, "Help: spp = 3 and depth = %d != 8\n!!", bit_depth); + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + return (PIX *)ERROR_PTR("not implemented for this depth", + procName, NULL); + } + + if (color_type == PNG_COLOR_TYPE_PALETTE || + color_type == PNG_COLOR_MASK_PALETTE) { /* generate a colormap */ + png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); + cmap = pixcmapCreate(d); /* spp == 1 */ + for (cindex = 0; cindex < num_palette; cindex++) { + rval = palette[cindex].red; + gval = palette[cindex].green; + bval = palette[cindex].blue; + pixcmapAddColor(cmap, rval, gval, bval); + } + } + else { + cmap = NULL; + } + + if ((pix = pixCreate(w, h, d)) == NULL) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + return (PIX *)ERROR_PTR("pix not made", procName, NULL); + } + pixSetInputFormat(pix, IFF_PNG); + wpl = pixGetWpl(pix); + data = pixGetData(pix); + pixSetColormap(pix, cmap); + pixSetSpp(pix, spp); + + if (spp == 1 && !tRNS) { /* copy straight from buffer to pix */ + for (i = 0; i < h; i++) { + line = data + i * wpl; + rowptr = row_pointers[i]; + for (j = 0; j < rowbytes; j++) { + SET_DATA_BYTE(line, j, rowptr[j]); + } + } + } + else if (spp == 2) { /* grayscale + alpha; convert to RGBA */ + L_INFO("converting (gray + alpha) ==> RGBA\n", procName); + for (i = 0; i < h; i++) { + ppixel = data + i * wpl; + rowptr = row_pointers[i]; + for (j = k = 0; j < w; j++) { + /* Copy gray value into r, g and b */ + SET_DATA_BYTE(ppixel, COLOR_RED, rowptr[k]); + SET_DATA_BYTE(ppixel, COLOR_GREEN, rowptr[k]); + SET_DATA_BYTE(ppixel, COLOR_BLUE, rowptr[k++]); + SET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL, rowptr[k++]); + ppixel++; + } + } + pixSetSpp(pix, 4); /* we do not support 2 spp pix */ + } + else if (spp == 3 || spp == 4) { + for (i = 0; i < h; i++) { + ppixel = data + i * wpl; + rowptr = row_pointers[i]; + for (j = k = 0; j < w; j++) { + SET_DATA_BYTE(ppixel, COLOR_RED, rowptr[k++]); + SET_DATA_BYTE(ppixel, COLOR_GREEN, rowptr[k++]); + SET_DATA_BYTE(ppixel, COLOR_BLUE, rowptr[k++]); + if (spp == 4) + SET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL, rowptr[k++]); + ppixel++; + } + } + } + + /* Special spp == 1 cases with transparency: + * (1) 8 bpp without colormap; assume full transparency + * (2) 1 bpp with colormap + trans array (for alpha) + * (3) 8 bpp with colormap + trans array (for alpha) + * These all require converting to RGBA */ + if (spp == 1 && tRNS) { + if (!cmap) { + /* Case 1: make fully transparent RGBA image */ + L_INFO("transparency, 1 spp, no colormap, no transparency array: " + "convention is fully transparent image\n", procName); + L_INFO("converting (fully transparent 1 spp) ==> RGBA\n", procName); + pixDestroy(&pix); + pix = pixCreate(w, h, 32); /* init to alpha = 0 (transparent) */ + pixSetSpp(pix, 4); + } + else { + L_INFO("converting (cmap + alpha) ==> RGBA\n", procName); + + /* Grab the transparency array */ + png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL); + if (!trans) { /* invalid png file */ + pixDestroy(&pix); + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + return (PIX *)ERROR_PTR("cmap, tRNS, but no transparency array", + procName, NULL); + } + + /* Save the cmap and destroy the pix */ + cmap = pixcmapCopy(pixGetColormap(pix)); + ncolors = pixcmapGetCount(cmap); + pixDestroy(&pix); + + /* Start over with 32 bit RGBA */ + pix = pixCreate(w, h, 32); + wpl = pixGetWpl(pix); + data = pixGetData(pix); + pixSetSpp(pix, 4); + +#if DEBUG_READ + fprintf(stderr, "ncolors = %d, num_trans = %d\n", + ncolors, num_trans); + for (i = 0; i < ncolors; i++) { + pixcmapGetColor(cmap, i, &rval, &gval, &bval); + if (i < num_trans) { + fprintf(stderr, "(r,g,b,a) = (%d,%d,%d,%d)\n", + rval, gval, bval, trans[i]); + } + else { + fprintf(stderr, "(r,g,b,a) = (%d,%d,%d,<<255>>)\n", + rval, gval, bval); + } + } +#endif /* DEBUG_READ */ + + /* Extract the data and convert to RGBA */ + if (d == 1) { + /* Case 2: 1 bpp with transparency (usually) behind white */ + L_INFO("converting 1 bpp cmap with alpha ==> RGBA\n", procName); + if (num_trans == 1) + L_INFO("num_trans = 1; second color opaque by default\n", + procName); + for (i = 0; i < h; i++) { + ppixel = data + i * wpl; + rowptr = row_pointers[i]; + for (j = 0, index = 0; j < rowbytes; j++) { + byte = rowptr[j]; + for (k = 0; k < 8 && index < w; k++, index++) { + bitval = (byte >> (7 - k)) & 1; + pixcmapGetColor(cmap, bitval, &rval, &gval, &bval); + composeRGBPixel(rval, gval, bval, ppixel); + SET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL, + bitval < num_trans ? trans[bitval] : 255); + ppixel++; + } + } + } + } + else if (d == 8) { + /* Case 3: 8 bpp with cmap and associated transparency */ + L_INFO("converting 8 bpp cmap with alpha ==> RGBA\n", procName); + for (i = 0; i < h; i++) { + ppixel = data + i * wpl; + rowptr = row_pointers[i]; + for (j = 0; j < w; j++) { + index = rowptr[j]; + pixcmapGetColor(cmap, index, &rval, &gval, &bval); + composeRGBPixel(rval, gval, bval, ppixel); + /* Assume missing entries to be 255 (opaque) + * according to the spec: + * http://www.w3.org/TR/PNG/#11tRNS */ + SET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL, + index < num_trans ? trans[index] : 255); + ppixel++; + } + } + } + else { + L_ERROR("spp == 1, cmap, trans array, invalid depth: %d\n", + procName, d); + } + pixcmapDestroy(&cmap); + } + } + +#if DEBUG_READ + if (cmap) { + for (i = 0; i < 16; i++) { + fprintf(stderr, "[%d] = %d\n", i, + ((l_uint8 *)(cmap->array))[i]); + } + } +#endif /* DEBUG_READ */ + + /* Final adjustments for bpp = 1. + * + If there is no colormap, the image must be inverted because + * png stores black pixels as 0. + * + We have already handled the case of cmapped, 1 bpp pix + * with transparency, where the output pix is 32 bpp RGBA. + * If there is no transparency but the pix has a colormap, + * we remove the colormap, because functions operating on + * 1 bpp images in leptonica assume no colormap. + * + The colormap must be removed in such a way that the pixel + * values are not changed. If the values are only black and + * white, we return a 1 bpp image; if gray, return an 8 bpp pix; + * otherwise, return a 32 bpp rgb pix. + * + * Note that we cannot use the PNG_TRANSFORM_INVERT_MONO flag + * to do the inversion, because that flag (since version 1.0.9) + * inverts 8 bpp grayscale as well, which we don't want to do. + * (It also doesn't work if there is a colormap.) + * + * Note that if the input png is a 1-bit with colormap and + * transparency, it has already been rendered as a 32 bpp, + * spp = 4 rgba pix. + */ + if (pixGetDepth(pix) == 1) { + if (!cmap) { + pixInvert(pix, pix); + } + else { + pixt = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC); + pixDestroy(&pix); + pix = pixt; + } + } + + xres = png_get_x_pixels_per_meter(png_ptr, info_ptr); + yres = png_get_y_pixels_per_meter(png_ptr, info_ptr); + pixSetXRes(pix, (l_int32)((l_float32)xres / 39.37 + 0.5)); /* to ppi */ + pixSetYRes(pix, (l_int32)((l_float32)yres / 39.37 + 0.5)); /* to ppi */ + + /* Get the text if there is any */ + png_get_text(png_ptr, info_ptr, &text_ptr, &num_text); + if (num_text && text_ptr) + pixSetText(pix, text_ptr->text); + + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + return pix; +} /*! @@ -1212,6 +1576,349 @@ char *text; } +/*! +* \brief pixWriteMemoryPng() +* +* \param[in] user_data +* \param[in] user_data_size +* \param[in] pix +* \param[in] gamma use 0.0 if gamma is not defined +* \return 0 if OK; 1 on error +* +*
+* Notes: +* (1) To do sequential writes of png format images to a stream, +* use pixWriteStreamPng() directly. +* (2) gamma is an optional png chunk. If no gamma value is to be +* placed into the file, use gamma = 0.0. Otherwise, if +* gamma > 0.0, its value is written into the header. +* (3) The use of gamma in png is highly problematic. For an illuminating +* discussion, see: http://hsivonen.iki.fi/png-gamma/ +* (4) What is the effect/meaning of gamma in the png file? This +* gamma, which we can call the 'source' gamma, is the +* inverse of the gamma that was used in enhance.c to brighten +* or darken images. The 'source' gamma is supposed to indicate +* the intensity mapping that was done at the time the +* image was captured. Display programs typically apply a +* 'display' gamma of 2.2 to the output, which is intended +* to linearize the intensity based on the response of +* thermionic tubes (CRTs). Flat panel LCDs have typically +* been designed to give a similar response as CRTs (call it +* "backward compatibility"). The 'display' gamma is +* in some sense the inverse of the 'source' gamma. +* jpeg encoders attached to scanners and cameras will lighten +* the pixels, applying a gamma corresponding to approximately +* a square-root relation of output vs input: +* output = input^(gamma) +* where gamma is often set near 0.4545 (1/gamma is 2.2). +* This is stored in the image file. Then if the display +* program reads the gamma, it will apply a display gamma, +* typically about 2.2; the product is 1.0, and the +* display program produces a linear output. This works because +* the dark colors were appropriately boosted by the scanner, +* as described by the 'source' gamma, so they should not +* be further boosted by the display program. +* (6) As an example, with xv and display, if no gamma is stored, +* the program acts as if gamma were 0.4545, multiplies this by 2.2, +* and does a linear rendering. Taking this as a baseline +* brightness, if the stored gamma is: +* > 0.4545, the image is rendered lighter than baseline +* < 0.4545, the image is rendered darker than baseline +* In contrast, gqview seems to ignore the gamma chunk in png. +* (5) The only valid pixel depths in leptonica are 1, 2, 4, 8, 16 +* and 32. However, it is possible, and in some cases desirable, +* to write out a png file using an rgb pix that has 24 bpp. +* For example, the open source xpdf SplashBitmap class generates +* 24 bpp rgb images. Consequently, we enable writing 24 bpp pix. +* To generate such a pix, you can make a 24 bpp pix without data +* and assign the data array to the pix; e.g., +* pix = pixCreateHeader(w, h, 24); +* pixSetData(pix, rgbdata); +* See pixConvert32To24() for an example, where we get rgbdata +* from the 32 bpp pix. Caution: do not call pixSetPadBits(), +* because the alignment is wrong and you may erase part of the +* last pixel on each line. +* (6) If the pix has a colormap, it is written to file. In most +* situations, the alpha component is 255 for each colormap entry, +* which is opaque and indicates that it should be ignored. +* However, if any alpha component is not 255, it is assumed that +* the alpha values are valid, and they are written to the png +* file in a tRNS segment. On readback, the tRNS segment is +* identified, and the colormapped image with alpha is converted +* to a 4 spp rgba image. +*+*/ +l_int32 +pixWriteMemoryPng(l_uint8** user_data, + size_t* user_data_size, + PIX *pix, + l_float32 gamma) +{ + l_int32 ret; + char commentstring[] = "Comment"; + l_int32 i, j, k; + l_int32 wpl, d, spp, cmflag, opaque; + l_int32 ncolors, compval; + l_int32 *rmap, *gmap, *bmap, *amap; + l_uint32 *data, *ppixel; + png_byte bit_depth, color_type; + png_byte alpha[256]; + png_uint_32 w, h; + png_uint_32 xres, yres; + png_bytep *row_pointers; + png_bytep rowbuffer; + png_structp png_ptr; + png_infop info_ptr; + png_colorp palette; + PIX *pixt; + PIXCMAP *cmap; + char *text; + MEMIODATA state; + + PROCNAME("pixWriteMemoryPng"); + + if (!user_data) + return ERROR_INT("user_data not defined", procName, 1); + if (!user_data_size) + return ERROR_INT("user_data_size not defined", procName, 1); + if (!pix) + return ERROR_INT("pix not defined", procName, 1); + + *user_data_size = 0; + *user_data = 0; + + state.m_Buffer = 0; + state.m_Size = 0; + state.m_Next = 0; + state.m_Count = 0; + state.m_Last = &state; + + /* Allocate the 2 data structures */ + if ((png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + (png_voidp)NULL, NULL, NULL)) == NULL) + return ERROR_INT("png_ptr not made", procName, 1); + + if ((info_ptr = png_create_info_struct(png_ptr)) == NULL) { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + return ERROR_INT("info_ptr not made", procName, 1); + } + + /* Set up png setjmp error handling */ + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return ERROR_INT("internal png error", procName, 1); + } + + png_set_write_fn(png_ptr, &state, memio_png_write_data, (png_flush_ptr)NULL); + + /* With best zlib compression (9), get between 1 and 10% improvement + * over default (6), but the compression is 3 to 10 times slower. + * Use the zlib default (6) as our default compression unless + * pix->special falls in the range [10 ... 19]; then subtract 10 + * to get the compression value. */ + compval = Z_DEFAULT_COMPRESSION; + if (pix->special >= 10 && pix->special < 20) + compval = pix->special - 10; + png_set_compression_level(png_ptr, compval); + + w = pixGetWidth(pix); + h = pixGetHeight(pix); + d = pixGetDepth(pix); + spp = pixGetSpp(pix); + if ((cmap = pixGetColormap(pix))) + cmflag = 1; + else + cmflag = 0; + + /* Set the color type and bit depth. */ + if (d == 32 && spp == 4) { + bit_depth = 8; + color_type = PNG_COLOR_TYPE_RGBA; /* 6 */ + cmflag = 0; /* ignore if it exists */ + } + else if (d == 24 || d == 32) { + bit_depth = 8; + color_type = PNG_COLOR_TYPE_RGB; /* 2 */ + cmflag = 0; /* ignore if it exists */ + } + else { + bit_depth = d; + color_type = PNG_COLOR_TYPE_GRAY; /* 0 */ + } + if (cmflag) + color_type = PNG_COLOR_TYPE_PALETTE; /* 3 */ + +#if DEBUG_WRITE + fprintf(stderr, "cmflag = %d, bit_depth = %d, color_type = %d\n", + cmflag, bit_depth, color_type); +#endif /* DEBUG_WRITE */ + + png_set_IHDR(png_ptr, info_ptr, w, h, bit_depth, color_type, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + + /* Store resolution in ppm, if known */ + xres = (png_uint_32)(39.37 * (l_float32)pixGetXRes(pix) + 0.5); + yres = (png_uint_32)(39.37 * (l_float32)pixGetYRes(pix) + 0.5); + if ((xres == 0) || (yres == 0)) + png_set_pHYs(png_ptr, info_ptr, 0, 0, PNG_RESOLUTION_UNKNOWN); + else + png_set_pHYs(png_ptr, info_ptr, xres, yres, PNG_RESOLUTION_METER); + + if (cmflag) { + pixcmapToArrays(cmap, &rmap, &gmap, &bmap, &amap); + ncolors = pixcmapGetCount(cmap); + pixcmapIsOpaque(cmap, &opaque); + + /* Make and save the palette */ + if ((palette = (png_colorp)(LEPT_CALLOC(ncolors, sizeof(png_color)))) + == NULL) + { + memio_free(&state); + return ERROR_INT("palette not made", procName, 1); + } + + for (i = 0; i < ncolors; i++) { + palette[i].red = (png_byte)rmap[i]; + palette[i].green = (png_byte)gmap[i]; + palette[i].blue = (png_byte)bmap[i]; + alpha[i] = (png_byte)amap[i]; + } + + png_set_PLTE(png_ptr, info_ptr, palette, (int)ncolors); + if (!opaque) /* alpha channel has some transparency; assume valid */ + png_set_tRNS(png_ptr, info_ptr, (png_bytep)alpha, + (int)ncolors, NULL); + LEPT_FREE(rmap); + LEPT_FREE(gmap); + LEPT_FREE(bmap); + LEPT_FREE(amap); + } + + /* 0.4545 is treated as the default by some image + * display programs (not gqview). A value > 0.4545 will + * lighten an image as displayed by xv, display, etc. */ + if (gamma > 0.0) + png_set_gAMA(png_ptr, info_ptr, (l_float64)gamma); + + if ((text = pixGetText(pix))) { + png_text text_chunk; + text_chunk.compression = PNG_TEXT_COMPRESSION_NONE; + text_chunk.key = commentstring; + text_chunk.text = text; + text_chunk.text_length = strlen(text); +#ifdef PNG_ITXT_SUPPORTED + text_chunk.itxt_length = 0; + text_chunk.lang = NULL; + text_chunk.lang_key = NULL; +#endif + png_set_text(png_ptr, info_ptr, &text_chunk, 1); + } + + /* Write header and palette info */ + png_write_info(png_ptr, info_ptr); + + if ((d != 32) && (d != 24)) { /* not rgb color */ + /* Generate a temporary pix with bytes swapped. + * For writing a 1 bpp image as png: + * ~ if no colormap, invert the data, because png writes + * black as 0 + * ~ if colormapped, do not invert the data; the two RGBA + * colors can have any value. */ + if (d == 1 && !cmap) { + pixt = pixInvert(NULL, pix); + pixEndianByteSwap(pixt); + } + else { + pixt = pixEndianByteSwapNew(pix); + } + if (!pixt) { + png_destroy_write_struct(&png_ptr, &info_ptr); + memio_free(&state); + return ERROR_INT("pixt not made", procName, 1); + } + + /* Make and assign array of image row pointers */ + if ((row_pointers = (png_bytep *)LEPT_CALLOC(h, sizeof(png_bytep))) + == NULL) + { + memio_free(&state); + return ERROR_INT("row-pointers not made", procName, 1); + } + + wpl = pixGetWpl(pixt); + data = pixGetData(pixt); + for (i = 0; i < h; i++) + row_pointers[i] = (png_bytep)(data + i * wpl); + png_set_rows(png_ptr, info_ptr, row_pointers); + + /* Transfer the data */ + png_write_image(png_ptr, row_pointers); + png_write_end(png_ptr, info_ptr); + + if (cmflag) + LEPT_FREE(palette); + LEPT_FREE(row_pointers); + pixDestroy(&pixt); + png_destroy_write_struct(&png_ptr, &info_ptr); + + memio_png_flush(&state); + *user_data = (l_uint8*)state.m_Buffer; + state.m_Buffer = 0; + *user_data_size = state.m_Count; + memio_free(&state); + + return 0; + } + + /* For rgb, compose and write a row at a time */ + data = pixGetData(pix); + wpl = pixGetWpl(pix); + if (d == 24) { /* See note 7 above: special case of 24 bpp rgb */ + for (i = 0; i < h; i++) { + ppixel = data + i * wpl; + png_write_rows(png_ptr, (png_bytepp)&ppixel, 1); + } + } + else { /* 32 bpp rgb and rgba. Write out the alpha channel if either + * the pix has 4 spp or writing it is requested anyway */ + if ((rowbuffer = (png_bytep)LEPT_CALLOC(w, 4)) == NULL) + { + memio_free(&state); + return ERROR_INT("rowbuffer not made", procName, 1); + } + + for (i = 0; i < h; i++) { + ppixel = data + i * wpl; + for (j = k = 0; j < w; j++) { + rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_RED); + rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_GREEN); + rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_BLUE); + if (spp == 4) + rowbuffer[k++] = GET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL); + ppixel++; + } + + png_write_rows(png_ptr, &rowbuffer, 1); + } + LEPT_FREE(rowbuffer); + } + + png_write_end(png_ptr, info_ptr); + + if (cmflag) + LEPT_FREE(palette); + png_destroy_write_struct(&png_ptr, &info_ptr); + + memio_png_flush(&state); + *user_data = (l_uint8*)state.m_Buffer; + state.m_Buffer = 0; + *user_data_size = state.m_Count; + memio_free(&state); + return 0; +} + + /*! * \brief pixSetZlibCompression() * @@ -1287,23 +1994,18 @@ PIX * pixReadMemPng(const l_uint8 *data, size_t size) { -FILE *fp; PIX *pix; PROCNAME("pixReadMemPng"); if (!data) return (PIX *)ERROR_PTR("cdata not defined", procName, NULL); - - if ((fp = fopenReadFromMemory(data, size)) == NULL) - return (PIX *)ERROR_PTR("stream not opened", procName, NULL); - pix = pixReadStreamPng(fp); - fclose(fp); + /* TDH */ + pix = pixReadMemoryPng(data, size); if (!pix) L_ERROR("pix not read\n", procName); return pix; } - /*! * \brief pixWriteMemPng() * @@ -1325,9 +2027,6 @@ pixWriteMemPng(l_uint8 **pdata, PIX *pix, l_float32 gamma) { -l_int32 ret; -FILE *fp; - PROCNAME("pixWriteMemPng"); if (pdata) *pdata = NULL; @@ -1339,25 +2038,7 @@ FILE *fp; if (!pix) return ERROR_INT("&pix not defined", procName, 1 ); -#if HAVE_FMEMOPEN - if ((fp = open_memstream((char **)pdata, psize)) == NULL) - return ERROR_INT("stream not opened", procName, 1); - ret = pixWriteStreamPng(fp, pix, gamma); -#else - L_INFO("work-around: writing to a temp file\n", procName); - #ifdef _WIN32 - if ((fp = fopenWriteWinTempfile()) == NULL) - return ERROR_INT("tmpfile stream not opened", procName, 1); - #else - if ((fp = tmpfile()) == NULL) - return ERROR_INT("tmpfile stream not opened", procName, 1); - #endif /* _WIN32 */ - ret = pixWriteStreamPng(fp, pix, gamma); - rewind(fp); - *pdata = l_binaryReadStream(fp, psize); -#endif /* HAVE_FMEMOPEN */ - fclose(fp); - return ret; + return pixWriteMemoryPng(pdata, psize, pix, gamma); } /* --------------------------------------------*/