| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- /* gif.c - Handles output to gif file */
- /*
- libzint - the open source barcode library
- Copyright (C) 2009-2024 Robin Stuart <rstuart114@gmail.com>
- 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 above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- 3. Neither the name of the project nor the names of its contributors
- may be used to endorse or promote products derived from this software
- without specific prior written permission.
- 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 THE COPYRIGHT OWNER OR 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.
- */
- /* SPDX-License-Identifier: BSD-3-Clause */
- #include <errno.h>
- #include <stdio.h>
- #include "common.h"
- #include "filemem.h"
- #include "output.h"
- /* Set LZW buffer paging size to this in expectation that compressed data will fit for typical scalings */
- #define GIF_LZW_PAGE_SIZE 0x100000 /* Megabyte */
- struct gif_state {
- struct filemem *fmp;
- unsigned char *pOut;
- const unsigned char *pIn;
- const unsigned char *pInEnd;
- size_t OutLength;
- size_t OutPosCur;
- size_t OutByteCountPos;
- unsigned short ClearCode;
- unsigned short FreeCode;
- char fByteCountByteSet;
- char fOutPaged;
- unsigned char OutBitsFree;
- unsigned short NodeAxon[4096];
- unsigned short NodeNext[4096];
- unsigned char NodePix[4096];
- unsigned char map[256];
- };
- static void gif_BufferNextByte(struct gif_state *pState) {
- (pState->OutPosCur)++;
- if (pState->fOutPaged && pState->OutPosCur + 2 >= pState->OutLength) {
- /* Keep last 256 bytes so `OutByteCountPos` within range */
- fm_write(pState->pOut, 1, pState->OutPosCur - 256, pState->fmp);
- memmove(pState->pOut, pState->pOut + pState->OutPosCur - 256, 256);
- pState->OutByteCountPos -= pState->OutPosCur - 256;
- pState->OutPosCur = 256;
- }
- /* Check if this position is a byte count position
- * `fByteCountByteSet` indicates, if byte count position bytes should be
- * inserted in general.
- * If this is true, and the distance to the last byte count position is 256
- * (e.g. 255 bytes in between), a byte count byte is inserted, and the value
- * of the last one is set to 255.
- * */
- if (pState->fByteCountByteSet && (pState->OutByteCountPos + 256 == pState->OutPosCur)) {
- (pState->pOut)[pState->OutByteCountPos] = 255;
- pState->OutByteCountPos = pState->OutPosCur;
- (pState->OutPosCur)++;
- }
- (pState->pOut)[pState->OutPosCur] = 0x00;
- }
- static void gif_AddCodeToBuffer(struct gif_state *pState, unsigned short CodeIn, unsigned char CodeBits) {
- /* Check, if we may fill up the current byte completely */
- if (CodeBits >= pState->OutBitsFree) {
- (pState->pOut)[pState->OutPosCur] |= (unsigned char) (CodeIn << (8 - pState->OutBitsFree));
- gif_BufferNextByte(pState);
- CodeIn = (unsigned short) (CodeIn >> pState->OutBitsFree);
- CodeBits -= pState->OutBitsFree;
- pState->OutBitsFree = 8;
- /* Write a full byte if there are at least 8 code bits left */
- if (CodeBits >= pState->OutBitsFree) {
- (pState->pOut)[pState->OutPosCur] = (unsigned char) CodeIn;
- gif_BufferNextByte(pState);
- CodeIn = (unsigned short) (CodeIn >> 8);
- CodeBits -= 8;
- }
- }
- /* The remaining bits of CodeIn fit in the current byte. */
- if (CodeBits > 0) {
- (pState->pOut)[pState->OutPosCur] |= (unsigned char) (CodeIn << (8 - pState->OutBitsFree));
- pState->OutBitsFree -= CodeBits;
- }
- }
- static void gif_FlushStringTable(struct gif_state *pState) {
- unsigned short Pos;
- for (Pos = 0; Pos < pState->ClearCode; Pos++) {
- (pState->NodeAxon)[Pos] = 0;
- }
- }
- static unsigned short gif_FindPixelOutlet(struct gif_state *pState, unsigned short HeadNode, unsigned char Byte) {
- unsigned short Outlet;
- Outlet = (pState->NodeAxon)[HeadNode];
- while (Outlet) {
- if ((pState->NodePix)[Outlet] == Byte)
- return Outlet;
- Outlet = (pState->NodeNext)[Outlet];
- }
- return 0;
- }
- static int gif_NextCode(struct gif_state *pState, unsigned char *pPixelValueCur, unsigned char CodeBits) {
- unsigned short UpNode;
- unsigned short DownNode;
- /* start with the root node for last pixel chain */
- UpNode = *pPixelValueCur;
- if (pState->pIn == pState->pInEnd) {
- gif_AddCodeToBuffer(pState, UpNode, CodeBits);
- return 0;
- }
- *pPixelValueCur = pState->map[*pState->pIn++];
- /* Follow the string table and the data stream to the end of the longest string that has a code */
- while (0 != (DownNode = gif_FindPixelOutlet(pState, UpNode, *pPixelValueCur))) {
- UpNode = DownNode;
- if (pState->pIn == pState->pInEnd) {
- gif_AddCodeToBuffer(pState, UpNode, CodeBits);
- return 0;
- }
- *pPixelValueCur = pState->map[*pState->pIn++];
- }
- /* Submit 'UpNode' which is the code of the longest string */
- gif_AddCodeToBuffer(pState, UpNode, CodeBits);
- /* ... and extend the string by appending 'PixelValueCur' */
- /* Create a successor node for 'PixelValueCur' whose code is 'freecode' */
- (pState->NodePix)[pState->FreeCode] = *pPixelValueCur;
- (pState->NodeAxon)[pState->FreeCode] = (pState->NodeNext)[pState->FreeCode] = 0;
- /* ...and link it to the end of the chain emanating from fg_axon[UpNode]. */
- DownNode = (pState->NodeAxon)[UpNode];
- if (!DownNode) {
- (pState->NodeAxon)[UpNode] = pState->FreeCode;
- } else {
- while ((pState->NodeNext)[DownNode]) {
- DownNode = (pState->NodeNext)[DownNode];
- }
- (pState->NodeNext)[DownNode] = pState->FreeCode;
- }
- return 1;
- }
- static int gif_lzw(struct gif_state *pState, int paletteBitSize) {
- unsigned char PixelValueCur;
- unsigned char CodeBits;
- unsigned short Pos;
- /* > Get first data byte */
- if (pState->pIn == pState->pInEnd)
- return 0;
- PixelValueCur = pState->map[*pState->pIn++];
- /* Number of bits per data item (=pixel)
- * We need at least a value of 2, otherwise the cc and eoi code consumes
- * the whole string table
- */
- if (paletteBitSize == 1)
- paletteBitSize = 2;
- /* initial size of compression codes */
- CodeBits = paletteBitSize + 1;
- pState->ClearCode = (1 << paletteBitSize);
- pState->FreeCode = pState->ClearCode + 2;
- pState->OutBitsFree = 8;
- pState->OutPosCur = 0;
- pState->fByteCountByteSet = 0;
- for (Pos = 0; Pos < pState->ClearCode; Pos++)
- (pState->NodePix)[Pos] = (unsigned char) Pos;
- gif_FlushStringTable(pState);
- /* Write what the GIF specification calls the "code size". */
- (pState->pOut)[pState->OutPosCur] = paletteBitSize;
- /* Reserve first bytecount byte */
- gif_BufferNextByte(pState);
- pState->OutByteCountPos = pState->OutPosCur;
- gif_BufferNextByte(pState);
- pState->fByteCountByteSet = 1;
- /* Submit one 'ClearCode' as the first code */
- gif_AddCodeToBuffer(pState, pState->ClearCode, CodeBits);
- for (;;) {
- /* generate and save the next code, which may consist of multiple input pixels. */
- if (!gif_NextCode(pState, &PixelValueCur, CodeBits)) { /* Check for end of data stream */
- /* submit 'eoi' as the last item of the code stream */
- gif_AddCodeToBuffer(pState, (unsigned short) (pState->ClearCode + 1), CodeBits);
- pState->fByteCountByteSet = 0;
- if (pState->OutBitsFree < 8) {
- gif_BufferNextByte(pState);
- }
- /* > Update last bytecount byte; */
- if (pState->OutByteCountPos < pState->OutPosCur) {
- (pState->pOut)[pState->OutByteCountPos]
- = (unsigned char) (pState->OutPosCur - pState->OutByteCountPos - 1);
- }
- pState->OutPosCur++;
- return 1;
- }
- /* Check for currently last code */
- if (pState->FreeCode == (1U << CodeBits))
- CodeBits++;
- pState->FreeCode++;
- /* Check for full stringtable - for widest compatibility with gif decoders, empty when 0xfff, not 0x1000 */
- if (pState->FreeCode == 0xfff) {
- gif_FlushStringTable(pState);
- gif_AddCodeToBuffer(pState, pState->ClearCode, CodeBits);
- CodeBits = (unsigned char) (1 + paletteBitSize);
- pState->FreeCode = (unsigned short) (pState->ClearCode + 2);
- }
- }
- }
- /*
- * Called function to save in gif format
- */
- INTERNAL int gif_pixel_plot(struct zint_symbol *symbol, unsigned char *pixelbuf) {
- struct filemem fm;
- unsigned char outbuf[10];
- unsigned char paletteRGB[10][3];
- int paletteCount, i;
- int paletteBitSize;
- int paletteSize;
- struct gif_state State;
- int transparent_index;
- int bgindex = -1, fgindex = -1;
- static const unsigned char RGBUnused[3] = {0,0,0};
- unsigned char RGBfg[3];
- unsigned char RGBbg[3];
- unsigned char fgalpha;
- unsigned char bgalpha;
- const size_t bitmapSize = (size_t) symbol->bitmap_height * symbol->bitmap_width;
- (void) out_colour_get_rgb(symbol->fgcolour, &RGBfg[0], &RGBfg[1], &RGBfg[2], &fgalpha);
- (void) out_colour_get_rgb(symbol->bgcolour, &RGBbg[0], &RGBbg[1], &RGBbg[2], &bgalpha);
- /* prepare state array */
- State.pIn = pixelbuf;
- State.pInEnd = pixelbuf + bitmapSize;
- /* Allow for overhead of 4 == code size + byte count + overflow byte + zero terminator */
- State.OutLength = bitmapSize + 4;
- State.fOutPaged = State.OutLength > GIF_LZW_PAGE_SIZE;
- if (State.fOutPaged) {
- State.OutLength = GIF_LZW_PAGE_SIZE;
- }
- if (!(State.pOut = (unsigned char *) malloc(State.OutLength))) {
- return errtxt(ZINT_ERROR_MEMORY, symbol, 614, "Insufficient memory for GIF LZW buffer");
- }
- State.fmp = &fm;
- /* Open output file in binary mode */
- if (!fm_open(State.fmp, symbol, "wb")) {
- errtxtf(0, symbol, 611, "Could not open GIF output file (%1$d: %2$s)", State.fmp->err,
- strerror(State.fmp->err));
- free(State.pOut);
- return ZINT_ERROR_FILE_ACCESS;
- }
- /*
- * Build a table of the used palette items.
- * Currently, there are the following 10 colour codes:
- * '0': standard background
- * '1': standard foreground
- * 'W': white
- * 'C': cyan
- * 'B': blue
- * 'M': magenta
- * 'R': red
- * 'Y': yellow
- * 'G': green
- * 'K': black
- * '0' and '1' may be identical to one of the other values
- */
- memset(State.map, 0, sizeof(State.map));
- if (symbol->symbology == BARCODE_ULTRA) {
- static const unsigned char ultra_chars[8] = { 'W', 'C', 'B', 'M', 'R', 'Y', 'G', 'K' };
- for (i = 0; i < 8; i++) {
- State.map[ultra_chars[i]] = i;
- out_colour_char_to_rgb(ultra_chars[i], &paletteRGB[i][0], &paletteRGB[i][1], &paletteRGB[i][2]);
- }
- paletteCount = 8;
- paletteBitSize = 3;
- /* For Ultracode, have foreground only if have bind/box */
- if (symbol->border_width > 0 && (symbol->output_options & (BARCODE_BIND | BARCODE_BOX | BARCODE_BIND_TOP))) {
- /* Check whether can re-use black */
- if (RGBfg[0] == 0 && RGBfg[1] == 0 && RGBfg[2] == 0) {
- State.map['1'] = fgindex = 7; /* Re-use black */
- } else {
- State.map['1'] = fgindex = paletteCount;
- memcpy(paletteRGB[paletteCount++], RGBfg, 3);
- paletteBitSize = 4;
- }
- }
- /* For Ultracode, have background only if have whitespace/quiet zones */
- if (symbol->whitespace_width > 0 || symbol->whitespace_height > 0
- || ((symbol->output_options & BARCODE_QUIET_ZONES)
- && !(symbol->output_options & BARCODE_NO_QUIET_ZONES))) {
- /* Check whether can re-use white */
- if (RGBbg[0] == 0xff && RGBbg[1] == 0xff && RGBbg[2] == 0xff && bgalpha == fgalpha) {
- State.map['0'] = bgindex = 0; /* Re-use white */
- } else {
- State.map['0'] = bgindex = paletteCount;
- memcpy(paletteRGB[paletteCount++], RGBbg, 3);
- paletteBitSize = 4;
- }
- }
- } else {
- State.map['0'] = bgindex = 0;
- memcpy(paletteRGB[bgindex], RGBbg, 3);
- State.map['1'] = fgindex = 1;
- memcpy(paletteRGB[fgindex], RGBfg, 3);
- paletteCount = 2;
- paletteBitSize = 1;
- }
- /* Set transparency */
- /* Note: does not allow both transparent foreground and background -
- * background takes priority */
- transparent_index = -1;
- if (bgalpha == 0 && bgindex != -1) {
- /* Transparent background */
- transparent_index = bgindex;
- } else if (fgalpha == 0 && fgindex != -1) {
- /* Transparent foreground */
- transparent_index = fgindex;
- }
- /* palette size 2 ^ bit size */
- paletteSize = 1 << paletteBitSize;
- /* GIF signature (6) */
- fm_write(transparent_index == -1 ? "GIF87a" : "GIF89a", 1, 6, State.fmp);
- /* Screen Descriptor (7) */
- /* Screen Width */
- outbuf[0] = (unsigned char) (0xff & symbol->bitmap_width);
- outbuf[1] = (unsigned char) (0xff & (symbol->bitmap_width >> 8));
- /* Screen Height */
- outbuf[2] = (unsigned char) (0xff & symbol->bitmap_height);
- outbuf[3] = (unsigned char) (0xff & (symbol->bitmap_height >> 8));
- /* write ImageBits-1 to the three least significant bits of byte 5 of
- * the Screen Descriptor
- * Bits 76543210
- * 1 : Global colour map
- * 111 : 8 bit colour depth of the palette
- * 0 : Not ordered in decreasing importance
- * xxx : palette bit size - 1
- */
- outbuf[4] = (unsigned char) (0xf0 | (0x7 & (paletteBitSize - 1)));
- /*
- * Background colour index
- * Default to 0. If colour code 0 or K is present, it is used as index
- */
- outbuf[5] = bgindex == -1 ? 0 : bgindex;
- /* Byte 7 must be 0x00 */
- outbuf[6] = 0x00;
- fm_write(outbuf, 1, 7, State.fmp);
- /* Global Color Table (paletteSize*3) */
- fm_write(paletteRGB, 1, 3*paletteCount, State.fmp);
- /* add unused palette items to fill palette size */
- for (i = paletteCount; i < paletteSize; i++) {
- fm_write(RGBUnused, 1, 3, State.fmp);
- }
- /* Graphic control extension (8) */
- /* A graphic control extension block is used for overlay gifs.
- * This is necessary to define a transparent color.
- */
- if (transparent_index != -1) {
- /* Extension Introducer = '!' */
- outbuf[0] = '!';
- /* Graphic Control Label */
- outbuf[1] = 0xf9;
- /* Block Size */
- outbuf[2] = 4;
- /* Packet fields:
- * 3 Reserved
- * 3 Disposal Method: 0 No Action, 1 No Dispose, 2: Background, 3: Prev.
- * 1 User Input Flag: 0: no user input, 1: user input
- * 1 Transparent Color Flag: 0: No Transparency, 1: Transparency index
- */
- outbuf[3] = 1;
- /* Delay Time */
- outbuf[4] = 0;
- outbuf[5] = 0;
- /* Transparent Color Index */
- outbuf[6] = (unsigned char) transparent_index;
- /* Block Terminator */
- outbuf[7] = 0;
- fm_write(outbuf, 1, 8, State.fmp);
- }
- /* Image Descriptor */
- /* Image separator character = ',' */
- outbuf[0] = ',';
- /* "Image Left" */
- outbuf[1] = 0x00;
- outbuf[2] = 0x00;
- /* "Image Top" */
- outbuf[3] = 0x00;
- outbuf[4] = 0x00;
- /* Image Width (low byte first) */
- outbuf[5] = (unsigned char) (0xff & symbol->bitmap_width);
- outbuf[6] = (unsigned char) (0xff & (symbol->bitmap_width >> 8));
- /* Image Height */
- outbuf[7] = (unsigned char) (0xff & symbol->bitmap_height);
- outbuf[8] = (unsigned char) (0xff & (symbol->bitmap_height >> 8));
- /* Byte 10 contains the interlaced flag and
- * information on the local color table.
- * There is no local color table if its most significant bit is reset.
- */
- outbuf[9] = 0x00;
- fm_write(outbuf, 1, 10, State.fmp);
- /* call lzw encoding */
- if (!gif_lzw(&State, paletteBitSize)) {
- free(State.pOut);
- (void) fm_close(State.fmp, symbol);
- return errtxt(ZINT_ERROR_MEMORY, symbol, 613, "Insufficient memory for GIF LZW buffer");
- }
- fm_write(State.pOut, 1, State.OutPosCur, State.fmp);
- free(State.pOut);
- /* GIF terminator */
- fm_putc(';', State.fmp);
- if (fm_error(State.fmp)) {
- errtxtf(0, symbol, 615, "Incomplete write of GIF output (%1$d: %2$s)", State.fmp->err,
- strerror(State.fmp->err));
- (void) fm_close(State.fmp, symbol);
- return ZINT_ERROR_FILE_WRITE;
- }
- if (!fm_close(State.fmp, symbol)) {
- return errtxtf(ZINT_ERROR_FILE_WRITE, symbol, 617, "Failure on closing GIF output file (%1$d: %2$s)",
- State.fmp->err, strerror(State.fmp->err));
- }
- return 0;
- }
- /* vim: set ts=4 sw=4 et : */
|