Update libnsgif (#2699)
* libnsgif: Rename upstream README with markdown extension. * libnsgif: Drop default frame delay patch * libnsgif: Remove unused utils / log.h header. * libnsgif: Rename files to match upstream. * libnsgif: Extend update script to update local README and test/ * libnsgif: Run update script. * libnsgif: Update buildsystem for renamed files * nsgifload: Switch to new library header. * nsgifload: Remove optional and unused bitmap callbacks. * nsgifload: Update to use new libnsgif API.
This commit is contained in:
parent
716fbf75b1
commit
8e9fe72dea
@ -1,8 +1,8 @@
|
||||
noinst_LTLIBRARIES = libnsgif.la
|
||||
|
||||
MY_SOURCES = \
|
||||
libnsgif.h \
|
||||
libnsgif.c \
|
||||
nsgif.h \
|
||||
gif.c \
|
||||
lzw.c \
|
||||
lzw.h
|
||||
|
||||
@ -11,11 +11,10 @@ libnsgif_la_SOURCES = $(MY_SOURCES)
|
||||
endif
|
||||
|
||||
EXTRA_DIST = \
|
||||
README-ns \
|
||||
README-ns.md \
|
||||
README.md \
|
||||
patches \
|
||||
update.sh \
|
||||
utils
|
||||
update.sh
|
||||
|
||||
if !ENABLE_NSGIF
|
||||
EXTRA_DIST += \
|
||||
|
@ -1,36 +0,0 @@
|
||||
libnsgif - Decoding GIF files
|
||||
=============================
|
||||
|
||||
The functions provided by this library allow for efficient progressive
|
||||
GIF decoding. Whilst the initialisation does not ensure that there is
|
||||
sufficient image data to complete the entire frame, it does ensure
|
||||
that the information provided is valid. Any subsequent attempts to
|
||||
decode an initialised GIF are guaranteed to succeed, and any bytes of
|
||||
the image not present are assumed to be totally transparent.
|
||||
|
||||
To begin decoding a GIF, the 'gif' structure must be initialised with
|
||||
the 'gif_data' and 'buffer_size' set to their initial values. The
|
||||
'buffer_position' should initially be 0, and will be internally
|
||||
updated as the decoding commences. The caller should then repeatedly
|
||||
call gif_initialise() with the structure until the function returns 1,
|
||||
or no more data is avaliable.
|
||||
|
||||
Once the initialisation has begun, the decoder completes the variables
|
||||
'frame_count' and 'frame_count_partial'. The former being the total
|
||||
number of frames that have been successfully initialised, and the
|
||||
latter being the number of frames that a partial amount of data is
|
||||
available for. This assists the caller in managing the animation
|
||||
whilst decoding is continuing.
|
||||
|
||||
To decode a frame, the caller must use gif_decode_frame() which
|
||||
updates the current 'frame_image' to reflect the desired frame. The
|
||||
required 'disposal_method' is also updated to reflect how the frame
|
||||
should be plotted. The caller must not assume that the current
|
||||
'frame_image' will be valid between calls if initialisation is still
|
||||
occuring, and should either always request that the frame is decoded
|
||||
(no processing will occur if the 'decoded_frame' has not been
|
||||
invalidated by initialisation) or perform the check itself.
|
||||
|
||||
It should be noted that gif_finalise() should always be called, even
|
||||
if no frames were initialised. Additionally, it is the responsibility
|
||||
of the caller to free 'gif_data'.
|
121
libvips/foreign/libnsgif/README-ns.md
Normal file
121
libvips/foreign/libnsgif/README-ns.md
Normal file
@ -0,0 +1,121 @@
|
||||
LibNSGIF: NetSurf GIF decoder
|
||||
=============================
|
||||
|
||||
LibNSGIF is a C library for decoding GIF format images and animations.
|
||||
It is licenced under the MIT licence.
|
||||
|
||||
This library aims to provide a simple API for robust decoding of GIF files.
|
||||
|
||||
Details
|
||||
-------
|
||||
|
||||
The GIF source data is scanned prior to decoding, allowing for efficient
|
||||
decoding. The scanning phase will scan currently available data and will
|
||||
resume from where it left off when called with additional data.
|
||||
|
||||
Only one frame is ever fully decoded to a bitmap at a time, reducing memory
|
||||
usage for large GIFs.
|
||||
|
||||
Using
|
||||
-----
|
||||
|
||||
LibNSGIF allows the client to allocate the bitmap into which the GIF is
|
||||
decoded. The client can have an arbitrary bitmap structure, that is simply
|
||||
a void pointer to LibNSGIF. The client must provide a callback table for
|
||||
interacting with bitmaps. This table must include as a minimum functions to
|
||||
create and destroy bitmaps, and a function to get a pointer to the bitmap's
|
||||
pixel data buffer.
|
||||
|
||||
To load a GIF, first create an nsgif object with `nsgif_create()`.
|
||||
|
||||
```c
|
||||
err = nsgif_create(&bitmap_callbacks, &gif);
|
||||
if (err != NSGIF_OK) {
|
||||
fprintf(stderr, "%s\n", nsgif_strerror(err));
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
Now you can load the GIF source data into the nsgif object with
|
||||
`nsgif_data_scan()`:
|
||||
|
||||
```c
|
||||
err = nsgif_data_scan(gif, size, data);
|
||||
if (err != NSGIF_OK) {
|
||||
fprintf(stderr, "%s\n", nsgif_strerror(err));
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
This scans the source data and decodes information about each frame, however
|
||||
it doesn't decode any of the bitmap data for the frames. The client may call
|
||||
`nsgif_data_scan()` multiple times as source data is fetched. Once the
|
||||
function has returned `NSGIF_OK` it has enough data to display at least one
|
||||
frame. The early frames can be decoded before the later frames are scanned.
|
||||
Frames have to be scanned before they can be decoded.
|
||||
|
||||
> **Note**: The client must not free the data until after calling
|
||||
> `nsgif_destroy()`. You can move the data, e.g. if you realloc to a bigger
|
||||
> buffer. Just be sure to call `nsgif_data_scan()` again with the new pointer
|
||||
> before making any other calls against that nsgif object.
|
||||
|
||||
To decode the frames, you can call `nsgif_get_info()` to get the frame_count,
|
||||
and then call `nsgif_frame_decode()` for each frame, and manage the animation,
|
||||
and non-displayable frames yourself, or you can use the helper function,
|
||||
`nsgif_frame_prepare()`:
|
||||
|
||||
```c
|
||||
err = nsgif_frame_prepare(gif, &area, &delay_cs, &frame_new);
|
||||
if (err != NSGIF_OK) {
|
||||
fprintf(stderr, "%s\n", nsgif_strerror(err));
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Update our bitmap to know it should be showing `frame_new` now.
|
||||
// Trigger redraw of `area` of image.
|
||||
|
||||
if (delay_cs != NSGIF_INFINITE) {
|
||||
// Schedule next frame in delay_cs.
|
||||
}
|
||||
```
|
||||
|
||||
This will return the number of the next frame to be decoded, the delay in cs
|
||||
before the next frame should be decoded, and the area of the bitmap that needs
|
||||
to be redrawn.
|
||||
|
||||
> **Note**: GIF frames may only occupy a portion of the overall bitmap, and only
|
||||
> redrawing the area that has changed may be more efficient than redrawing the
|
||||
> whole thing. The returned area comprises both any region that has been
|
||||
> changed in the disposal of the previous frame and the new frame.
|
||||
|
||||
GIF files can limit the number of animation loops to a finite number or they
|
||||
may only have one frame. In either of these cases, the returned delay is
|
||||
`NSGIF_INFINITE` indicating that the animation is complete. Subsequent calls
|
||||
to `nsgif_frame_prepare()` will return `NSGIF_ERR_ANIMATION_END`.
|
||||
|
||||
To force the repeat of an animation, call `nsgif_reset()`.
|
||||
|
||||
One reason for the two-step decoding of frames is that it enables deferred
|
||||
decoding. You can call `nsgif_frame_prepare()` and cause a redraw of that
|
||||
portion of your document. If the GIF is off screen (another tab, or scrolled
|
||||
out of sight), there is no need to decode it at all.
|
||||
|
||||
Once the bitmap is needed for a redraw, you can decode the correct frame
|
||||
on-demand with:
|
||||
|
||||
```c
|
||||
err = nsgif_frame_decode(gif, frame_new, &bitmap);
|
||||
if (err != NSGIF_OK) {
|
||||
fprintf(stderr, "%s\n", nsgif_strerror(err));
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
Note that this will be a no-op if the requested frame already happens to be
|
||||
the decoded frame.
|
||||
|
||||
Once you are done with the GIF, free up the nsgif object with:
|
||||
|
||||
```c
|
||||
nsgif_destroy(gif);
|
||||
```
|
1791
libvips/foreign/libnsgif/gif.c
Normal file
1791
libvips/foreign/libnsgif/gif.c
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,194 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004 Richard Wilson <richard.wilson@netsurf-browser.org>
|
||||
* Copyright 2008 Sean Fox <dyntryx@gmail.com>
|
||||
* Copyright 2013-2021 Michael Drake <tlsa@netsurf-browser.org>
|
||||
*
|
||||
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
|
||||
* Licenced under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Interface to progressive animated GIF file decoding.
|
||||
*/
|
||||
|
||||
#ifndef _LIBNSGIF_H_
|
||||
#define _LIBNSGIF_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* Error return values */
|
||||
typedef enum {
|
||||
GIF_WORKING = 1,
|
||||
GIF_OK = 0,
|
||||
GIF_INSUFFICIENT_DATA = -1,
|
||||
GIF_INSUFFICIENT_FRAME_DATA = GIF_INSUFFICIENT_DATA,
|
||||
GIF_FRAME_DATA_ERROR = -2,
|
||||
GIF_DATA_ERROR = -4,
|
||||
GIF_INSUFFICIENT_MEMORY = -5,
|
||||
GIF_FRAME_NO_DISPLAY = -6,
|
||||
GIF_END_OF_FRAME = -7
|
||||
} gif_result;
|
||||
|
||||
/** GIF frame data */
|
||||
typedef struct gif_frame {
|
||||
/** whether the frame should be displayed/animated */
|
||||
bool display;
|
||||
/** delay (in cs) before animating the frame */
|
||||
uint32_t frame_delay;
|
||||
|
||||
/* Internal members are listed below */
|
||||
|
||||
/** offset (in bytes) to the GIF frame data */
|
||||
uint32_t frame_pointer;
|
||||
/** whether the frame has previously been used */
|
||||
bool virgin;
|
||||
/** whether the frame is totally opaque */
|
||||
bool opaque;
|
||||
/** whether a full image redraw is required */
|
||||
bool redraw_required;
|
||||
/** how the previous frame should be disposed; affects plotting */
|
||||
uint8_t disposal_method;
|
||||
/** whether we acknowledge transparency */
|
||||
bool transparency;
|
||||
/** the index designating a transparent pixel */
|
||||
uint32_t transparency_index;
|
||||
/** x co-ordinate of redraw rectangle */
|
||||
uint32_t redraw_x;
|
||||
/** y co-ordinate of redraw rectangle */
|
||||
uint32_t redraw_y;
|
||||
/** width of redraw rectangle */
|
||||
uint32_t redraw_width;
|
||||
/** height of redraw rectangle */
|
||||
uint32_t redraw_height;
|
||||
/* Frame flags */
|
||||
uint32_t flags;
|
||||
} gif_frame;
|
||||
|
||||
/* API for Bitmap callbacks */
|
||||
typedef void* (*gif_bitmap_cb_create)(int width, int height);
|
||||
typedef void (*gif_bitmap_cb_destroy)(void *bitmap);
|
||||
typedef uint8_t* (*gif_bitmap_cb_get_buffer)(void *bitmap);
|
||||
typedef void (*gif_bitmap_cb_set_opaque)(void *bitmap, bool opaque);
|
||||
typedef bool (*gif_bitmap_cb_test_opaque)(void *bitmap);
|
||||
typedef void (*gif_bitmap_cb_modified)(void *bitmap);
|
||||
|
||||
/** Bitmap callbacks function table */
|
||||
typedef struct gif_bitmap_callback_vt {
|
||||
/** Create a bitmap. */
|
||||
gif_bitmap_cb_create bitmap_create;
|
||||
/** Free a bitmap. */
|
||||
gif_bitmap_cb_destroy bitmap_destroy;
|
||||
/** Return a pointer to the pixel data in a bitmap. */
|
||||
gif_bitmap_cb_get_buffer bitmap_get_buffer;
|
||||
|
||||
/* Members below are optional */
|
||||
|
||||
/** Sets whether a bitmap should be plotted opaque. */
|
||||
gif_bitmap_cb_set_opaque bitmap_set_opaque;
|
||||
/** Tests whether a bitmap has an opaque alpha channel. */
|
||||
gif_bitmap_cb_test_opaque bitmap_test_opaque;
|
||||
/** The bitmap image has changed, so flush any persistent cache. */
|
||||
gif_bitmap_cb_modified bitmap_modified;
|
||||
} gif_bitmap_callback_vt;
|
||||
|
||||
/** GIF animation data */
|
||||
typedef struct gif_animation {
|
||||
/** LZW decode context */
|
||||
void *lzw_ctx;
|
||||
/** callbacks for bitmap functions */
|
||||
gif_bitmap_callback_vt bitmap_callbacks;
|
||||
/** pointer to GIF data */
|
||||
const uint8_t *gif_data;
|
||||
/** width of GIF (may increase during decoding) */
|
||||
uint32_t width;
|
||||
/** height of GIF (may increase during decoding) */
|
||||
uint32_t height;
|
||||
/** number of frames decoded */
|
||||
uint32_t frame_count;
|
||||
/** number of frames partially decoded */
|
||||
uint32_t frame_count_partial;
|
||||
/** decoded frames */
|
||||
gif_frame *frames;
|
||||
/** current frame decoded to bitmap */
|
||||
int decoded_frame;
|
||||
/** currently decoded image; stored as bitmap from bitmap_create callback */
|
||||
void *frame_image;
|
||||
/** number of times to loop animation */
|
||||
int loop_count;
|
||||
|
||||
/* Internal members are listed below */
|
||||
|
||||
/** current index into GIF data */
|
||||
uint32_t buffer_position;
|
||||
/** total number of bytes of GIF data available */
|
||||
uint32_t buffer_size;
|
||||
/** current number of frame holders */
|
||||
uint32_t frame_holders;
|
||||
/** background index */
|
||||
uint32_t bg_index;
|
||||
/** background colour */
|
||||
uint32_t bg_colour;
|
||||
/** image aspect ratio (ignored) */
|
||||
uint32_t aspect_ratio;
|
||||
/** size of colour table (in entries) */
|
||||
uint32_t colour_table_size;
|
||||
/** whether the GIF has a global colour table */
|
||||
bool global_colours;
|
||||
/** global colour table */
|
||||
uint32_t *global_colour_table;
|
||||
/** local colour table */
|
||||
uint32_t *local_colour_table;
|
||||
/** current colour table */
|
||||
uint32_t *colour_table;
|
||||
|
||||
/** previous frame for GIF_FRAME_RESTORE */
|
||||
void *prev_frame;
|
||||
/** previous frame index */
|
||||
int prev_index;
|
||||
/** previous frame width */
|
||||
unsigned prev_width;
|
||||
/** previous frame height */
|
||||
unsigned prev_height;
|
||||
} gif_animation;
|
||||
|
||||
/**
|
||||
* Initialises necessary gif_animation members.
|
||||
*/
|
||||
void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks);
|
||||
|
||||
/**
|
||||
* Initialises any workspace held by the animation and attempts to decode
|
||||
* any information that hasn't already been decoded.
|
||||
* If an error occurs, all previously decoded frames are retained.
|
||||
*
|
||||
* \return Error return value.
|
||||
* - GIF_FRAME_DATA_ERROR for GIF frame data error
|
||||
* - GIF_INSUFFICIENT_DATA reached unexpected end of source data
|
||||
* - GIF_INSUFFICIENT_MEMORY for memory error
|
||||
* - GIF_DATA_ERROR for GIF error
|
||||
* - GIF_OK for successful decoding
|
||||
* - GIF_WORKING for successful decoding if more frames are expected
|
||||
*/
|
||||
gif_result gif_initialise(gif_animation *gif, size_t size, const uint8_t *data);
|
||||
|
||||
/**
|
||||
* Decodes a GIF frame.
|
||||
*
|
||||
* \return Error return value.
|
||||
* - GIF_FRAME_DATA_ERROR for GIF frame data error
|
||||
* - GIF_DATA_ERROR for GIF error (invalid frame header)
|
||||
* - GIF_INSUFFICIENT_DATA reached unexpected end of source data
|
||||
* - GIF_INSUFFICIENT_MEMORY for insufficient memory to process
|
||||
* - GIF_OK for successful decoding
|
||||
*/
|
||||
gif_result gif_decode_frame(gif_animation *gif, uint32_t frame);
|
||||
|
||||
/**
|
||||
* Releases any workspace held by a gif
|
||||
*/
|
||||
void gif_finalise(gif_animation *gif);
|
||||
|
||||
#endif
|
@ -1,6 +1,6 @@
|
||||
nsgif_lib = static_library('nsgif',
|
||||
'libnsgif.h',
|
||||
'libnsgif.c',
|
||||
'nsgif.h',
|
||||
'gif.c',
|
||||
'lzw.c',
|
||||
'lzw.h',
|
||||
)
|
||||
|
358
libvips/foreign/libnsgif/nsgif.h
Normal file
358
libvips/foreign/libnsgif/nsgif.h
Normal file
@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright 2004 Richard Wilson <richard.wilson@netsurf-browser.org>
|
||||
* Copyright 2008 Sean Fox <dyntryx@gmail.com>
|
||||
* Copyright 2013-2022 Michael Drake <tlsa@netsurf-browser.org>
|
||||
*
|
||||
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
|
||||
* Licenced under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Interface to progressive animated GIF file decoding.
|
||||
*/
|
||||
|
||||
#ifndef NSNSGIF_H
|
||||
#define NSNSGIF_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
/** Representation of infinity. */
|
||||
#define NSGIF_INFINITE (UINT32_MAX)
|
||||
|
||||
/**
|
||||
* Opaque type used by LibNSGIF to represent a GIF object in memory.
|
||||
*/
|
||||
typedef struct nsgif nsgif_t;
|
||||
|
||||
/**
|
||||
* LibNSGIF rectangle structure.
|
||||
*
|
||||
* * Top left coordinate is `(x0, y0)`.
|
||||
* * Width is `x1 - x0`.
|
||||
* * Height is `y1 - y0`.
|
||||
* * Units are pixels.
|
||||
*/
|
||||
typedef struct nsgif_rect {
|
||||
/** x co-ordinate of redraw rectangle, left */
|
||||
uint32_t x0;
|
||||
/** y co-ordinate of redraw rectangle, top */
|
||||
uint32_t y0;
|
||||
/** x co-ordinate of redraw rectangle, right */
|
||||
uint32_t x1;
|
||||
/** y co-ordinate of redraw rectangle, bottom */
|
||||
uint32_t y1;
|
||||
} nsgif_rect_t;
|
||||
|
||||
/**
|
||||
* LibNSGIF return codes.
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
* Success.
|
||||
*/
|
||||
NSGIF_OK,
|
||||
|
||||
/**
|
||||
* Out of memory error.
|
||||
*/
|
||||
NSGIF_ERR_OOM,
|
||||
|
||||
/**
|
||||
* GIF source data is invalid, and no frames are recoverable.
|
||||
*/
|
||||
NSGIF_ERR_DATA,
|
||||
|
||||
/**
|
||||
* Frame number is not valid.
|
||||
*/
|
||||
NSGIF_ERR_BAD_FRAME,
|
||||
|
||||
/**
|
||||
* GIF source data contained an error in a frame.
|
||||
*/
|
||||
NSGIF_ERR_DATA_FRAME,
|
||||
|
||||
/**
|
||||
* Too many frames.
|
||||
*/
|
||||
NSGIF_ERR_FRAME_COUNT,
|
||||
|
||||
/**
|
||||
* GIF source data ended without one complete frame available.
|
||||
*/
|
||||
NSGIF_ERR_END_OF_DATA,
|
||||
|
||||
/**
|
||||
* GIF source data ended with incomplete frame.
|
||||
*/
|
||||
NSGIF_ERR_END_OF_FRAME,
|
||||
|
||||
/**
|
||||
* The current frame cannot be displayed.
|
||||
*/
|
||||
NSGIF_ERR_FRAME_DISPLAY,
|
||||
|
||||
/**
|
||||
* Indicates an animation is complete, and \ref nsgif_reset must be
|
||||
* called to restart the animation from the beginning.
|
||||
*/
|
||||
NSGIF_ERR_ANIMATION_END,
|
||||
} nsgif_error;
|
||||
|
||||
/**
|
||||
* Client bitmap type.
|
||||
*
|
||||
* These are client-created and destroyed, via the \ref bitmap callbacks,
|
||||
* but they are owned by a \ref nsgif_t.
|
||||
*
|
||||
* The pixel buffer is is 32bpp, treated as individual bytes in the component
|
||||
* order RR GG BB AA. For example, a 1x1 image with a single orange pixel would
|
||||
* be encoded as the following sequence of bytes: 0xff, 0x88, 0x00, 0x00.
|
||||
*/
|
||||
typedef void nsgif_bitmap_t;
|
||||
|
||||
/** Bitmap callbacks function table */
|
||||
typedef struct nsgif_bitmap_cb_vt {
|
||||
/**
|
||||
* Callback to create a bitmap with the given dimensions.
|
||||
*
|
||||
* \param[in] width Required bitmap width in pixels.
|
||||
* \param[in] height Required bitmap height in pixels.
|
||||
* \return pointer to client's bitmap structure or NULL on error.
|
||||
*/
|
||||
nsgif_bitmap_t* (*create)(int width, int height);
|
||||
|
||||
/**
|
||||
* Callback to free a bitmap.
|
||||
*
|
||||
* \param[in] bitmap The bitmap to destroy.
|
||||
*/
|
||||
void (*destroy)(nsgif_bitmap_t *bitmap);
|
||||
|
||||
/**
|
||||
* Get pointer to pixel buffer in a bitmap.
|
||||
*
|
||||
* The pixel buffer must be `width * height * sizeof(uint32_t)`.
|
||||
* Note that the returned pointer to uint8_t must be 4-byte aligned.
|
||||
*
|
||||
* \param[in] bitmap The bitmap.
|
||||
* \return pointer to bitmap's pixel buffer.
|
||||
*/
|
||||
uint8_t* (*get_buffer)(nsgif_bitmap_t *bitmap);
|
||||
|
||||
/* The following functions are optional. */
|
||||
|
||||
/**
|
||||
* Set whether a bitmap can be plotted opaque.
|
||||
*
|
||||
* \param[in] bitmap The bitmap.
|
||||
* \param[in] opaque Whether the current frame is opaque.
|
||||
*/
|
||||
void (*set_opaque)(nsgif_bitmap_t *bitmap, bool opaque);
|
||||
|
||||
/**
|
||||
* Tests whether a bitmap has an opaque alpha channel.
|
||||
*
|
||||
* \param[in] bitmap The bitmap.
|
||||
* \return true if the bitmap is opaque, false otherwise.
|
||||
*/
|
||||
bool (*test_opaque)(nsgif_bitmap_t *bitmap);
|
||||
|
||||
/**
|
||||
* Bitmap modified notification.
|
||||
*
|
||||
* \param[in] bitmap The bitmap.
|
||||
*/
|
||||
void (*modified)(nsgif_bitmap_t *bitmap);
|
||||
} nsgif_bitmap_cb_vt;
|
||||
|
||||
/**
|
||||
* Convert an error code to a string.
|
||||
*
|
||||
* \param[in] err The error code to convert.
|
||||
* \return String representation of given error code.
|
||||
*/
|
||||
const char *nsgif_strerror(nsgif_error err);
|
||||
|
||||
/**
|
||||
* Create the NSGIF object.
|
||||
*
|
||||
* \param[in] bitmap_vt Bitmap operation functions v-table.
|
||||
* \param[out] gif_out Return \ref nsgif_t object on success.
|
||||
*
|
||||
* \return NSGIF_OK on success, or appropriate error otherwise.
|
||||
*/
|
||||
nsgif_error nsgif_create(
|
||||
const nsgif_bitmap_cb_vt *bitmap_vt,
|
||||
nsgif_t **gif_out);
|
||||
|
||||
/**
|
||||
* Free a NSGIF object.
|
||||
*
|
||||
* \param[in] gif The NSGIF to free.
|
||||
*/
|
||||
void nsgif_destroy(nsgif_t *gif);
|
||||
|
||||
/**
|
||||
* Scan the source image data.
|
||||
*
|
||||
* This is used to feed the source data into LibNSGIF. This must be called
|
||||
* before calling \ref nsgif_frame_decode.
|
||||
*
|
||||
* It can be called multiple times with, with increasing sizes. If it is called
|
||||
* several times, as more data is available (e.g. slow network fetch) the data
|
||||
* already given to \ref nsgif_data_scan must be provided each time.
|
||||
*
|
||||
* For example, if you call \ref nsgif_data_scan with 25 bytes of data, and then
|
||||
* fetch another 10 bytes, you would need to call \ref nsgif_data_scan with a
|
||||
* size of 35 bytes, and the whole 35 bytes must be contiguous memory. It is
|
||||
* safe to `realloc` the source buffer between calls to \ref nsgif_data_scan.
|
||||
* (The actual data pointer is allowed to be different.)
|
||||
*
|
||||
* If an error occurs, all previously scanned frames are retained.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object.
|
||||
* \param[in] size Number of bytes in data.
|
||||
* \param[in] data Raw source GIF data.
|
||||
*
|
||||
* \return NSGIF_OK on success, or appropriate error otherwise.
|
||||
*/
|
||||
nsgif_error nsgif_data_scan(
|
||||
nsgif_t *gif,
|
||||
size_t size,
|
||||
const uint8_t *data);
|
||||
|
||||
/**
|
||||
* Prepare to show a frame.
|
||||
*
|
||||
* If this is the last frame of an animation with a finite loop count, the
|
||||
* returned `delay_cs` will be \ref NSGIF_INFINITE, indicating that the frame
|
||||
* should be shown forever.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object.
|
||||
* \param[out] area The area in pixels that must be redrawn.
|
||||
* \param[out] delay_cs Time to wait after frame_new before next frame in cs.
|
||||
* \param[out] frame_new The frame to decode.
|
||||
*
|
||||
* \return NSGIF_OK on success, or appropriate error otherwise.
|
||||
*/
|
||||
nsgif_error nsgif_frame_prepare(
|
||||
nsgif_t *gif,
|
||||
nsgif_rect_t *area,
|
||||
uint32_t *delay_cs,
|
||||
uint32_t *frame_new);
|
||||
|
||||
/**
|
||||
* Decodes a GIF frame.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object.
|
||||
* \param[in] frame The frame number to decode.
|
||||
* \param[out] bitmap On success, returns pointer to the client-allocated,
|
||||
* nsgif-owned client bitmap structure.
|
||||
*
|
||||
* \return NSGIF_OK on success, or appropriate error otherwise.
|
||||
*/
|
||||
nsgif_error nsgif_frame_decode(
|
||||
nsgif_t *gif,
|
||||
uint32_t frame,
|
||||
nsgif_bitmap_t **bitmap);
|
||||
|
||||
/**
|
||||
* Reset a GIF animation.
|
||||
*
|
||||
* Some animations are only meant to loop N times, and then show the
|
||||
* final frame forever. This function resets the loop and frame counters,
|
||||
* so that the animation can be replayed without the overhead of recreating
|
||||
* the \ref nsgif_t object and rescanning the raw data.
|
||||
*
|
||||
* \param[in] gif A \ref nsgif_t object.
|
||||
*
|
||||
* \return NSGIF_OK on success, or appropriate error otherwise.
|
||||
*/
|
||||
nsgif_error nsgif_reset(
|
||||
nsgif_t *gif);
|
||||
|
||||
/**
|
||||
* Information about a GIF.
|
||||
*/
|
||||
typedef struct nsgif_info {
|
||||
/** width of GIF (may increase during decoding) */
|
||||
uint32_t width;
|
||||
/** height of GIF (may increase during decoding) */
|
||||
uint32_t height;
|
||||
/** number of frames decoded */
|
||||
uint32_t frame_count;
|
||||
/** number of times to loop animation */
|
||||
int loop_max;
|
||||
/** number of animation loops so far */
|
||||
int loop_count;
|
||||
/** background colour in same pixel format as \ref nsgif_bitmap_t. */
|
||||
uint8_t background[4];
|
||||
} nsgif_info_t;
|
||||
|
||||
/**
|
||||
* Frame disposal method.
|
||||
*
|
||||
* Clients do not need to know about this, it is provided purely for dumping
|
||||
* raw information about GIF frames.
|
||||
*/
|
||||
enum nsgif_disposal {
|
||||
NSGIF_DISPOSAL_UNSPECIFIED, /**< No disposal method specified. */
|
||||
NSGIF_DISPOSAL_NONE, /**< Frame remains. */
|
||||
NSGIF_DISPOSAL_RESTORE_BG, /**< Clear frame to background colour. */
|
||||
NSGIF_DISPOSAL_RESTORE_PREV, /**< Restore previous frame. */
|
||||
NSGIF_DISPOSAL_RESTORE_QUIRK, /**< Alias for NSGIF_DISPOSAL_RESTORE_PREV. */
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a disposal method to a string.
|
||||
*
|
||||
* \param[in] disposal The disposal method to convert.
|
||||
* \return String representation of given disposal method.
|
||||
*/
|
||||
const char *nsgif_str_disposal(enum nsgif_disposal disposal);
|
||||
|
||||
/**
|
||||
* Information about a GIF frame.
|
||||
*/
|
||||
typedef struct nsgif_frame_info {
|
||||
/** whether the frame should be displayed/animated */
|
||||
bool display;
|
||||
/** whether the frame may have transparency */
|
||||
bool transparency;
|
||||
|
||||
/** Disposal method for previous frame; affects plotting */
|
||||
uint8_t disposal;
|
||||
/** delay (in cs) before animating the frame */
|
||||
uint32_t delay;
|
||||
|
||||
/** Frame's redraw rectangle. */
|
||||
nsgif_rect_t rect;
|
||||
} nsgif_frame_info_t;
|
||||
|
||||
/**
|
||||
* Get information about a GIF from an \ref nsgif_t object.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object to get info for.
|
||||
*
|
||||
* \return The gif info, or NULL on error.
|
||||
*/
|
||||
const nsgif_info_t *nsgif_get_info(const nsgif_t *gif);
|
||||
|
||||
/**
|
||||
* Get information about a GIF from an \ref nsgif_t object.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object to get frame info for.
|
||||
* \param[in] frame The frame number to get info for.
|
||||
*
|
||||
* \return The gif frame info, or NULL on error.
|
||||
*/
|
||||
const nsgif_frame_info_t *nsgif_get_frame_info(
|
||||
const nsgif_t *gif,
|
||||
uint32_t frame);
|
||||
|
||||
#endif
|
@ -1,11 +0,0 @@
|
||||
--- libnsgif.orig.c 2021-12-18 17:58:43.829719505 +0000
|
||||
+++ libnsgif.c 2021-12-18 17:58:47.773783438 +0000
|
||||
@@ -1003,7 +1003,7 @@
|
||||
frame->frame_pointer = gif->buffer_position;
|
||||
frame->redraw_required = false;
|
||||
frame->disposal_method = 0;
|
||||
- frame->frame_delay = 100;
|
||||
+ frame->frame_delay = 10;
|
||||
frame->display = false;
|
||||
frame->virgin = true;
|
||||
}
|
763
libvips/foreign/libnsgif/test/cli.c
Normal file
763
libvips/foreign/libnsgif/test/cli.c
Normal file
@ -0,0 +1,763 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (C) 2021 Michael Drake <tlsa@netsurf-browser.org>
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Command line argument handling.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cli.h"
|
||||
|
||||
/**
|
||||
* Check whether a CLI argument type should have a numerical value.
|
||||
*
|
||||
* \param[in] type An argument type.
|
||||
* \return true if the argument needs a numerical value, or false otherwise.
|
||||
*/
|
||||
static inline bool cli__arg_is_numerical(enum cli_arg_type type)
|
||||
{
|
||||
return (type != CLI_STRING && type != CLI_BOOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a signed integer value from an argument.
|
||||
*
|
||||
* \param[in] str String containing value to parse.
|
||||
* \param[out] i Pointer to place to store parsed value.
|
||||
* \param[in,out] pos Current position in str, updated on exit.
|
||||
* \return true on success, or false otherwise.
|
||||
*/
|
||||
static bool cli__parse_value_int(
|
||||
const char *str,
|
||||
int64_t *i,
|
||||
size_t *pos)
|
||||
{
|
||||
long long temp;
|
||||
char *end = NULL;
|
||||
|
||||
str += *pos;
|
||||
errno = 0;
|
||||
temp = strtoll(str, &end, 0);
|
||||
|
||||
if (end == str || errno == ERANGE ||
|
||||
temp > INT64_MAX || temp < INT64_MIN) {
|
||||
fprintf(stderr, "Failed to parse integer from '%s'\n", str);
|
||||
return false;
|
||||
}
|
||||
|
||||
*i = (int64_t)temp;
|
||||
*pos += (size_t)(end - str);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an unsigned integer value from an argument.
|
||||
*
|
||||
* \param[in] str String containing value to parse.
|
||||
* \param[out] u Pointer to place to store parsed value.
|
||||
* \param[in,out] pos Current position in str, updated on exit.
|
||||
* \return true on success, or false otherwise.
|
||||
*/
|
||||
static bool cli__parse_value_uint(
|
||||
const char *str,
|
||||
uint64_t *u,
|
||||
size_t *pos)
|
||||
{
|
||||
unsigned long long temp;
|
||||
char *end = NULL;
|
||||
|
||||
str += *pos;
|
||||
errno = 0;
|
||||
temp = strtoull(str, &end, 0);
|
||||
|
||||
if (end == str || errno == ERANGE || temp > UINT64_MAX) {
|
||||
fprintf(stderr, "Failed to parse unsigned from '%s'\n", str);
|
||||
return false;
|
||||
}
|
||||
|
||||
*u = (uint64_t)temp;
|
||||
*pos += (size_t)(end - str);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an enum value from an argument.
|
||||
*
|
||||
* \param[in] str String containing value to parse.
|
||||
* \param[out] e Enum details.
|
||||
* \param[in,out] pos Current position in str, updated on exit.
|
||||
* \return true on success, or false otherwise.
|
||||
*/
|
||||
static bool cli__parse_value_enum(
|
||||
const char *str,
|
||||
const struct cli_enum *e,
|
||||
size_t *pos)
|
||||
{
|
||||
str += *pos;
|
||||
*pos += strlen(str);
|
||||
|
||||
for (const struct cli_str_val *sv = e->desc; sv->str != NULL; sv++) {
|
||||
if (strcmp(str, sv->str) == 0) {
|
||||
*e->e = sv->val;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string value from an argument.
|
||||
*
|
||||
* \param[in] str String containing value to parse.
|
||||
* \param[out] s Pointer to place to store parsed value.
|
||||
* \param[in,out] pos Current position in str, updated on exit.
|
||||
* \return true on success, or false otherwise.
|
||||
*/
|
||||
static bool cli__parse_value_string(
|
||||
const char *str,
|
||||
const char **s,
|
||||
size_t *pos)
|
||||
{
|
||||
*s = str + *pos;
|
||||
*pos += strlen(*s);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a value from an argument.
|
||||
*
|
||||
* \param[in] entry Client command line interface argument specification.
|
||||
* \param[in] arg Argument to parse a value from.
|
||||
* \param[in,out] pos Current position in argument, updated on exit.
|
||||
* \return true on success, or false otherwise.
|
||||
*/
|
||||
static bool cli__parse_value(
|
||||
const struct cli_table_entry *entry,
|
||||
const char *arg,
|
||||
size_t *pos)
|
||||
{
|
||||
switch (entry->t) {
|
||||
case CLI_CMD:
|
||||
if (strcmp(arg + *pos, entry->l) == 0) {
|
||||
*pos += strlen(arg);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
case CLI_INT:
|
||||
return cli__parse_value_int(arg, entry->v.i, pos);
|
||||
|
||||
case CLI_UINT:
|
||||
return cli__parse_value_uint(arg, entry->v.u, pos);
|
||||
|
||||
case CLI_ENUM:
|
||||
return cli__parse_value_enum(arg, &entry->v.e, pos);
|
||||
|
||||
case CLI_STRING:
|
||||
return cli__parse_value_string(arg, entry->v.s, pos);
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unexpected value for '%s': %s\n",
|
||||
entry->l, arg);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a value from an argument.
|
||||
*
|
||||
* \param[in] entry Client command line interface argument specification.
|
||||
* \param[in] argc Number of command line arguments.
|
||||
* \param[in] argv String vector containing command line arguments.
|
||||
* \param[in] arg_pos Current position in argv.
|
||||
* \param[in,out] pos Current pos in current argument, updated on exit.
|
||||
* \return true on success, or false otherwise.
|
||||
*/
|
||||
static bool cli__parse_argv_value(const struct cli_table_entry *entry,
|
||||
int argc, const char **argv,
|
||||
int arg_pos, size_t *pos)
|
||||
{
|
||||
const char *arg = argv[arg_pos];
|
||||
|
||||
if (arg_pos >= argc) {
|
||||
fprintf(stderr, "Value not given for '%s'\n", entry->l);
|
||||
return false;
|
||||
}
|
||||
|
||||
return cli__parse_value(entry, arg, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a CLI argument is a positional value.
|
||||
*
|
||||
* \param[in] entry Client command line interface argument specification.
|
||||
* \return true if the argument is positional, or false otherwise.
|
||||
*/
|
||||
static inline bool cli__entry_is_positional(const struct cli_table_entry *entry)
|
||||
{
|
||||
return entry->p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a short argument flag.
|
||||
*
|
||||
* \param[in] cli Client command line interface specification.
|
||||
* \param[in] s Argument flag to look up in client CLI spec.
|
||||
* \return Client CLI spec entry on success, or NULL otherwise.
|
||||
*/
|
||||
static const struct cli_table_entry *cli__lookup_short(
|
||||
const struct cli_table *cli, char s)
|
||||
{
|
||||
for (size_t i = 0; i < cli->count; i++) {
|
||||
if (cli__entry_is_positional(&cli->entries[i])) {
|
||||
continue;
|
||||
}
|
||||
if (cli->entries[i].s == s) {
|
||||
return &cli->entries[i];
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unknown flag: '%c'\n", s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an argument with a type that requires a value.
|
||||
*
|
||||
* This can handle the value being in the current argument, optionally split by
|
||||
* a separator, or in the next argument.
|
||||
*
|
||||
* \param[in] entry Client command line interface argument specification.
|
||||
* \param[in] argc Number of command line arguments.
|
||||
* \param[in] argv String vector containing command line arguments.
|
||||
* \param[in,out] arg_pos Current position in argv, updated on exit.
|
||||
* \param[in] pos Current position in current argument string.
|
||||
* \param[in] sep Name/value separator character, or '\0' if none.
|
||||
* \return true on success, or false otherwise.
|
||||
*/
|
||||
static bool cli__handle_arg_value(const struct cli_table_entry *entry,
|
||||
int argc, const char **argv, int *arg_pos, size_t pos, char sep)
|
||||
{
|
||||
const char *arg = argv[*arg_pos];
|
||||
size_t orig_pos;
|
||||
bool ret;
|
||||
|
||||
if (arg[pos] == '\0') {
|
||||
(*arg_pos)++;
|
||||
pos = 0;
|
||||
} else if (arg[pos] == sep) {
|
||||
pos++;
|
||||
} else if (cli__arg_is_numerical(entry->t) == false) {
|
||||
fprintf(stderr, "Separator required for non-numerical value\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isspace(argv[*arg_pos][pos])) {
|
||||
fprintf(stderr, "Unexpected white space in '%s' "
|
||||
"for argument '%s'\n",
|
||||
&argv[*arg_pos][pos], entry->l);
|
||||
return false;
|
||||
}
|
||||
|
||||
orig_pos = pos;
|
||||
ret = cli__parse_argv_value(entry, argc, argv, *arg_pos, &pos);
|
||||
if (ret != true) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (argv[*arg_pos][pos] != '\0') {
|
||||
fprintf(stderr, "Invalid value '%s' for argument '%s'\n",
|
||||
&argv[*arg_pos][orig_pos], entry->l);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a flags argument.
|
||||
*
|
||||
* \param[in] cli Client command line interface specification.
|
||||
* \param[in] argc Number of command line arguments.
|
||||
* \param[in] argv String vector containing command line arguments.
|
||||
* \param[out] arg_pos Current position in argv, updated on exit.
|
||||
* \return true on success, or false otherwise.
|
||||
*/
|
||||
static bool cli__parse_short(const struct cli_table *cli,
|
||||
int argc, const char **argv, int *arg_pos)
|
||||
{
|
||||
const char *arg = argv[*arg_pos];
|
||||
size_t pos = 1;
|
||||
|
||||
if (arg[0] != '-') {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (arg[pos] != '\0') {
|
||||
const struct cli_table_entry *entry;
|
||||
|
||||
entry = cli__lookup_short(cli, arg[pos]);
|
||||
if (entry == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry->t == CLI_BOOL) {
|
||||
*entry->v.b = true;
|
||||
} else {
|
||||
return cli__handle_arg_value(entry, argc, argv,
|
||||
arg_pos, pos + 1, '\0');
|
||||
}
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a long argument name.
|
||||
*
|
||||
* \param[in] cli Client command line interface specification.
|
||||
* \param[in] arg Argument name to look up in cli spec.
|
||||
* \param[in,out] pos Current position in arg, updated on exit.
|
||||
* \return Client CLI spec entry on success, or NULL otherwise.
|
||||
*/
|
||||
static const struct cli_table_entry *cli__lookup_long(
|
||||
const struct cli_table *cli,
|
||||
const char *arg,
|
||||
size_t *pos)
|
||||
{
|
||||
arg += *pos;
|
||||
|
||||
for (size_t i = 0; i < cli->count; i++) {
|
||||
if (cli__entry_is_positional(&cli->entries[i]) == false) {
|
||||
const char *name = cli->entries[i].l;
|
||||
size_t name_len = strlen(cli->entries[i].l);
|
||||
|
||||
if (strncmp(name, arg, name_len) == 0) {
|
||||
if (arg[name_len] != '\0' &&
|
||||
arg[name_len] != '=') {
|
||||
continue;
|
||||
}
|
||||
*pos += name_len;
|
||||
return &cli->entries[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unknown argument: '%s'\n", arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a long argument.
|
||||
*
|
||||
* \param[in] cli Client command line interface specification.
|
||||
* \param[in] argc Number of command line arguments.
|
||||
* \param[in] argv String vector containing command line arguments.
|
||||
* \param[out] arg_pos Current position in argv, updated on exit.
|
||||
* \return true on success, or false otherwise.
|
||||
*/
|
||||
static bool cli__parse_long(const struct cli_table *cli,
|
||||
int argc, const char **argv, int *arg_pos)
|
||||
{
|
||||
const struct cli_table_entry *entry;
|
||||
const char *arg = argv[*arg_pos];
|
||||
size_t pos = 2;
|
||||
|
||||
if (arg[0] != '-' ||
|
||||
arg[1] != '-') {
|
||||
return false;
|
||||
}
|
||||
|
||||
entry = cli__lookup_long(cli, arg, &pos);
|
||||
if (entry == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry->t == CLI_BOOL) {
|
||||
if (arg[pos] != '\0') {
|
||||
fprintf(stderr, "Unexpected value for argument '%s'\n",
|
||||
arg);
|
||||
return false;
|
||||
}
|
||||
*entry->v.b = true;
|
||||
} else {
|
||||
bool ret;
|
||||
|
||||
ret = cli__handle_arg_value(entry, argc, argv,
|
||||
arg_pos, pos, '=');
|
||||
if (ret != true) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a positional argument according to the given CLI spec entry.
|
||||
*
|
||||
* \param[in] entry Client command line interface argument specification.
|
||||
* \param[in] arg Argument to parse.
|
||||
* \return true on success, or false otherwise.
|
||||
*/
|
||||
static bool cli__parse_positional_entry(
|
||||
const struct cli_table_entry *entry,
|
||||
const char *arg)
|
||||
{
|
||||
size_t pos = 0;
|
||||
bool ret;
|
||||
|
||||
ret = cli__parse_value(entry, arg, &pos);
|
||||
if (ret != true) {
|
||||
return ret;
|
||||
} else if (arg[pos] != '\0') {
|
||||
fprintf(stderr, "Failed to parse value '%s' for arg '%s'\n",
|
||||
arg, entry->l);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a positional argument.
|
||||
*
|
||||
* \param[in] cli Client command line interface specification.
|
||||
* \param[in] arg Argument to parse.
|
||||
* \param[in] count Number of positional arguments parsed already.
|
||||
* \return true on success, or false otherwise.
|
||||
*/
|
||||
static bool cli__parse_positional(const struct cli_table *cli,
|
||||
const char *arg, size_t count)
|
||||
{
|
||||
size_t positional = 0;
|
||||
|
||||
for (size_t i = 0; i < cli->count; i++) {
|
||||
if (cli__entry_is_positional(&cli->entries[i])) {
|
||||
if (positional == count) {
|
||||
return cli__parse_positional_entry(
|
||||
&cli->entries[i], arg);
|
||||
}
|
||||
|
||||
positional++;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unexpected positional argument: '%s'\n", arg);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string to indicate type of value expected for an argument.
|
||||
*
|
||||
* \param[in] type The argument type.
|
||||
* \return String for value type.
|
||||
*/
|
||||
static const char *cli__string_from_type(enum cli_arg_type type)
|
||||
{
|
||||
static const char *const strings[] = {
|
||||
[CLI_BOOL] = "",
|
||||
[CLI_INT] = "INT",
|
||||
[CLI_UINT] = "UINT",
|
||||
[CLI_ENUM] = "ENUM",
|
||||
[CLI_STRING] = "STRING",
|
||||
};
|
||||
|
||||
if (type >= CLI_ARRAY_LEN(strings) || strings[type] == NULL) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return strings[type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to update a maximum adjusted string length if new values is greater.
|
||||
*
|
||||
* \param[in] str String to check.
|
||||
* \param[in] adjustment Amount to modify length of string by (bytes).
|
||||
* \param[out] len Returns the maximum of existing and this length.
|
||||
*/
|
||||
static void cli__max_len(const char *str, size_t adjustment, size_t *len)
|
||||
{
|
||||
size_t str_len = strlen(str) + adjustment;
|
||||
|
||||
if (str_len > *len) {
|
||||
*len = str_len;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count up various properties of the client CLI interface specification.
|
||||
*
|
||||
* \param[in] cli Client command line interface specification.
|
||||
* \param[out] count Returns number of non-positional arguments.
|
||||
* \param[out] pcount Returns number of positional arguments.
|
||||
* \param[out] max_len Returns max string length of non-positional arguments.
|
||||
* \param[out] pmax_len Returns max string length of positional arguments.
|
||||
* \param[out] phas_desc Returns number of positional args with descriptions.
|
||||
*/
|
||||
static void cli__count(const struct cli_table *cli,
|
||||
size_t *count,
|
||||
size_t *pcount,
|
||||
size_t *max_len,
|
||||
size_t *pmax_len,
|
||||
size_t *phas_desc)
|
||||
{
|
||||
if (count != NULL) *count = 0;
|
||||
if (pcount != NULL) *pcount = 0;
|
||||
if (max_len != NULL) *max_len = 0;
|
||||
if (pmax_len != NULL) *pmax_len = 0;
|
||||
if (phas_desc != NULL) *phas_desc = 0;
|
||||
|
||||
for (size_t i = 0; i < cli->count; i++) {
|
||||
const struct cli_table_entry *entry = &cli->entries[i];
|
||||
|
||||
if (cli__entry_is_positional(entry)) {
|
||||
if (pcount != NULL) {
|
||||
(*pcount)++;
|
||||
}
|
||||
if (pmax_len != NULL) {
|
||||
cli__max_len(entry->l, 0, pmax_len);
|
||||
}
|
||||
if (phas_desc != NULL) {
|
||||
(*phas_desc)++;
|
||||
}
|
||||
} else {
|
||||
if (count != NULL) {
|
||||
(*count)++;
|
||||
}
|
||||
if (max_len != NULL) {
|
||||
const char *type_str;
|
||||
size_t type_len;
|
||||
|
||||
type_str = cli__string_from_type(entry->t);
|
||||
type_len = strlen(type_str);
|
||||
|
||||
cli__max_len(entry->l, type_len, max_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool cli__is_negative(const char *arg)
|
||||
{
|
||||
int64_t i;
|
||||
size_t pos = 0;
|
||||
|
||||
return cli__parse_value_int(arg, &i, &pos)
|
||||
&& pos == strlen(arg)
|
||||
&& i < 0;
|
||||
}
|
||||
|
||||
/* Documented in cli.h */
|
||||
bool cli_parse(const struct cli_table *cli, int argc, const char **argv)
|
||||
{
|
||||
size_t pos_count = 0;
|
||||
enum {
|
||||
ARG_PROG_NAME,
|
||||
ARG_FIRST,
|
||||
};
|
||||
|
||||
for (int i = ARG_FIRST; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
size_t pos_inc = 0;
|
||||
bool ret;
|
||||
|
||||
if (arg[0] == '-') {
|
||||
if (arg[1] == '-') {
|
||||
ret = cli__parse_long(cli, argc, argv, &i);
|
||||
} else {
|
||||
ret = cli__parse_short(cli, argc, argv, &i);
|
||||
if (ret != true) {
|
||||
if (cli__is_negative(argv[i])) {
|
||||
pos_inc = 1;
|
||||
ret = cli__parse_positional(
|
||||
cli, argv[i],
|
||||
pos_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pos_inc = 1;
|
||||
ret = cli__parse_positional(cli, argv[i], pos_count);
|
||||
}
|
||||
|
||||
if (ret != true) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
pos_count += pos_inc;
|
||||
}
|
||||
|
||||
if (pos_count < cli->min_positional) {
|
||||
fprintf(stderr, "Insufficient positional arguments found.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get terminal width.
|
||||
*
|
||||
* \return terminal width in characters.
|
||||
*/
|
||||
static size_t cli__terminal_width(void)
|
||||
{
|
||||
return 80;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print an entry's description, with a given indent.
|
||||
*
|
||||
* The indent is assumed to already be applied for the first line of the
|
||||
* output by the caller.
|
||||
*
|
||||
* \param[in] entry The entry to print the description for.
|
||||
* \param[in] indent The number of spaces to pad the left margin with.
|
||||
*/
|
||||
static void cli__print_description(const struct cli_table_entry *entry,
|
||||
size_t indent)
|
||||
{
|
||||
size_t terminal_width = cli__terminal_width();
|
||||
size_t avail = (indent > terminal_width) ? 0 : terminal_width - indent;
|
||||
size_t space = avail;
|
||||
const char *desc = entry->d;
|
||||
|
||||
if (desc != NULL) {
|
||||
while (*desc != '\0') {
|
||||
size_t word_len = strcspn(desc, " \n\t");
|
||||
if (word_len <= space || space == avail) {
|
||||
fprintf(stderr, "%*.*s",
|
||||
(int)word_len,
|
||||
(int)word_len, desc);
|
||||
desc += word_len;
|
||||
if (word_len <= space) {
|
||||
space -= word_len;
|
||||
}
|
||||
if (space > 0) {
|
||||
fprintf(stderr, " ");
|
||||
space--;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "\n%*s", (int)indent, "");
|
||||
space = avail;
|
||||
}
|
||||
desc += strspn(desc, " \n\t");
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
/* Documented in cli.h */
|
||||
void cli_help(const struct cli_table *cli, const char *prog_name)
|
||||
{
|
||||
size_t count;
|
||||
size_t pcount;
|
||||
size_t max_len;
|
||||
size_t pmax_len;
|
||||
size_t phas_desc;
|
||||
size_t required = 0;
|
||||
enum {
|
||||
ARG_PROG_NAME,
|
||||
};
|
||||
|
||||
cli__count(cli, &count, &pcount, &max_len, &pmax_len, &phas_desc);
|
||||
|
||||
fprintf(stderr, "\nUsage: %s", prog_name);
|
||||
|
||||
if (pcount > 0) {
|
||||
for (size_t i = 0; i < cli->count; i++) {
|
||||
if (cli__entry_is_positional(&cli->entries[i])) {
|
||||
const char *punctuation =
|
||||
(required == cli->min_positional) ?
|
||||
" [" : " ";
|
||||
|
||||
if (cli->entries[i].t == CLI_CMD) {
|
||||
fprintf(stderr, "%s%s", punctuation,
|
||||
cli->entries[i].l);
|
||||
} else {
|
||||
fprintf(stderr, "%s<%s>", punctuation,
|
||||
cli->entries[i].l);
|
||||
}
|
||||
required++;
|
||||
}
|
||||
}
|
||||
if (required == pcount && required > cli->min_positional) {
|
||||
fprintf(stderr, "]");
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
fprintf(stderr, " [options]");
|
||||
}
|
||||
|
||||
fprintf(stderr, "\n\n");
|
||||
|
||||
if (phas_desc > 0) {
|
||||
fprintf(stderr, "Where:\n\n");
|
||||
|
||||
for (size_t i = 0; i < cli->count; i++) {
|
||||
const struct cli_table_entry *entry = &cli->entries[i];
|
||||
|
||||
if (entry->d == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cli__entry_is_positional(entry)) {
|
||||
fprintf(stderr, " %*.*s ",
|
||||
(int)pmax_len,
|
||||
(int)pmax_len,
|
||||
entry->l);
|
||||
cli__print_description(entry, pmax_len + 4);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
fprintf(stderr, "Options:\n\n");
|
||||
|
||||
for (size_t i = 0; i < cli->count; i++) {
|
||||
const struct cli_table_entry *entry = &cli->entries[i];
|
||||
const char *type_str;
|
||||
size_t type_len;
|
||||
size_t arg_len;
|
||||
|
||||
if (cli__entry_is_positional(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry->s != '\0') {
|
||||
fprintf(stderr, " -%c", entry->s);
|
||||
} else {
|
||||
fprintf(stderr, " ");
|
||||
}
|
||||
|
||||
type_str = cli__string_from_type(entry->t);
|
||||
type_len = strlen(type_str);
|
||||
arg_len = strlen(entry->l);
|
||||
|
||||
fprintf(stderr, " --%s %s%*.s ", entry->l, type_str,
|
||||
(int)(max_len - arg_len - type_len),
|
||||
"");
|
||||
cli__print_description(entry, max_len + 11);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
}
|
94
libvips/foreign/libnsgif/test/cli.h
Normal file
94
libvips/foreign/libnsgif/test/cli.h
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (C) 2021 Michael Drake <tlsa@netsurf-browser.org>
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Command line argument handling API.
|
||||
*/
|
||||
|
||||
#ifndef _PELTAR_CLI_H_
|
||||
#define _PELTAR_CLI_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* Helper to get element count for an array,
|
||||
*
|
||||
* \param[in] _a Array to get number of elements for.
|
||||
*/
|
||||
#define CLI_ARRAY_LEN(_a) ((sizeof(_a))/(sizeof(*(_a))))
|
||||
|
||||
/**
|
||||
* CLI argument type.
|
||||
*/
|
||||
enum cli_arg_type {
|
||||
CLI_CMD, /**< A sub-command. Must match long argument name. */
|
||||
CLI_BOOL, /**< Has no value; presence of flag indicates true. */
|
||||
CLI_INT, /**< Has signed integer value. */
|
||||
CLI_UINT, /**< Has unsigned integer value. */
|
||||
CLI_ENUM, /**< Has enumeration value. */
|
||||
CLI_STRING, /**< Has string value. */
|
||||
};
|
||||
|
||||
struct cli_str_val {
|
||||
const char *str;
|
||||
int64_t val;
|
||||
};
|
||||
|
||||
struct cli_enum {
|
||||
const struct cli_str_val *desc;
|
||||
int64_t *e; /**< Location to store \ref CLI_ENUM value. */
|
||||
};
|
||||
|
||||
/**
|
||||
* Client description for a command line argument.
|
||||
*/
|
||||
struct cli_table_entry {
|
||||
const char *l; /**< Long argument name. */
|
||||
const char s; /**< Short flag name. (Non-positional arguments.) */
|
||||
bool p; /**< Whether the argument is a positional argument. */
|
||||
enum cli_arg_type t; /**< Argument type. */
|
||||
union {
|
||||
bool *b; /**< Location to store \ref CLI_BOOL value. */
|
||||
int64_t *i; /**< Location to store \ref CLI_INT value. */
|
||||
uint64_t *u; /**< Location to store \ref CLI_UINT value. */
|
||||
const char **s; /**< Location to store \ref CLI_STRING value. */
|
||||
struct cli_enum e;
|
||||
} v; /**< Where to store type-specific values. */
|
||||
const char *d; /**< Description. */
|
||||
};
|
||||
|
||||
/**
|
||||
* Client command line interface specification.
|
||||
*/
|
||||
struct cli_table {
|
||||
const struct cli_table_entry *entries;
|
||||
size_t count;
|
||||
size_t min_positional;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the command line arguments.
|
||||
*
|
||||
* \param[in] cli Client command line interface specification.
|
||||
* \param[in] argc Number of command line arguments.
|
||||
* \param[in] argv String vector containing command line arguments.
|
||||
* \return true on success, false on error.
|
||||
*/
|
||||
bool cli_parse(const struct cli_table *cli, int argc, const char **argv);
|
||||
|
||||
/**
|
||||
* Print usage and help output.
|
||||
*
|
||||
* Note: Assumes non-Unicode. (One byte per character.)
|
||||
*
|
||||
* \param[in] cli Client command line interface specification.
|
||||
* \param[in] prog_name Program name.
|
||||
*/
|
||||
void cli_help(const struct cli_table *cli, const char *prog_name);
|
||||
|
||||
#endif
|
@ -1,258 +0,0 @@
|
||||
/* under libvips, compile with:
|
||||
*
|
||||
* gcc -g -Wall decode_gif.c `pkg-config vips --cflags --libs`
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2008 Sean Fox <dyntryx@gmail.com>
|
||||
* Copyright 2008 James Bursa <james@netsurf-browser.org>
|
||||
*
|
||||
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
|
||||
* Licenced under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
||||
#include <vips/vips.h>
|
||||
#include "../libnsgif.h"
|
||||
|
||||
#define BYTES_PER_PIXEL 4
|
||||
#define MAX_IMAGE_BYTES (48 * 1024 * 1024)
|
||||
|
||||
|
||||
static void *bitmap_create(int width, int height)
|
||||
{
|
||||
/* ensure a stupidly large bitmap is not created */
|
||||
if (((long long)width * (long long)height) > (MAX_IMAGE_BYTES/BYTES_PER_PIXEL)) {
|
||||
return NULL;
|
||||
}
|
||||
return calloc(width * height, BYTES_PER_PIXEL);
|
||||
}
|
||||
|
||||
|
||||
static void bitmap_set_opaque(void *bitmap, bool opaque)
|
||||
{
|
||||
(void) opaque; /* unused */
|
||||
(void) bitmap; /* unused */
|
||||
assert(bitmap);
|
||||
}
|
||||
|
||||
|
||||
static bool bitmap_test_opaque(void *bitmap)
|
||||
{
|
||||
(void) bitmap; /* unused */
|
||||
assert(bitmap);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static unsigned char *bitmap_get_buffer(void *bitmap)
|
||||
{
|
||||
assert(bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
|
||||
static void bitmap_destroy(void *bitmap)
|
||||
{
|
||||
assert(bitmap);
|
||||
free(bitmap);
|
||||
}
|
||||
|
||||
|
||||
static void bitmap_modified(void *bitmap)
|
||||
{
|
||||
(void) bitmap; /* unused */
|
||||
assert(bitmap);
|
||||
return;
|
||||
}
|
||||
|
||||
static unsigned char *load_file(const char *path, size_t *data_size)
|
||||
{
|
||||
FILE *fd;
|
||||
struct stat sb;
|
||||
unsigned char *buffer;
|
||||
size_t size;
|
||||
size_t n;
|
||||
|
||||
fd = fopen(path, "rb");
|
||||
if (!fd) {
|
||||
perror(path);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (stat(path, &sb)) {
|
||||
perror(path);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
size = sb.st_size;
|
||||
|
||||
buffer = malloc(size);
|
||||
if (!buffer) {
|
||||
fprintf(stderr, "Unable to allocate %lld bytes\n",
|
||||
(long long) size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
n = fread(buffer, 1, size, fd);
|
||||
if (n != size) {
|
||||
perror(path);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
fclose(fd);
|
||||
|
||||
*data_size = size;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
static void warning(const char *context, gif_result code)
|
||||
{
|
||||
fprintf(stderr, "%s failed: ", context);
|
||||
switch (code)
|
||||
{
|
||||
case GIF_INSUFFICIENT_FRAME_DATA:
|
||||
fprintf(stderr, "GIF_INSUFFICIENT_FRAME_DATA");
|
||||
break;
|
||||
case GIF_FRAME_DATA_ERROR:
|
||||
fprintf(stderr, "GIF_FRAME_DATA_ERROR");
|
||||
break;
|
||||
case GIF_INSUFFICIENT_DATA:
|
||||
fprintf(stderr, "GIF_INSUFFICIENT_DATA");
|
||||
break;
|
||||
case GIF_DATA_ERROR:
|
||||
fprintf(stderr, "GIF_DATA_ERROR");
|
||||
break;
|
||||
case GIF_INSUFFICIENT_MEMORY:
|
||||
fprintf(stderr, "GIF_INSUFFICIENT_MEMORY");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "unknown code %i", code);
|
||||
break;
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
static void write_ppm(FILE* fh, const char *name, gif_animation *gif,
|
||||
bool no_write)
|
||||
{
|
||||
unsigned int i;
|
||||
gif_result code;
|
||||
|
||||
if (!no_write) {
|
||||
fprintf(fh, "P3\n");
|
||||
fprintf(fh, "# %s\n", name);
|
||||
fprintf(fh, "# width %u \n", gif->width);
|
||||
fprintf(fh, "# height %u \n", gif->height);
|
||||
fprintf(fh, "# frame_count %u \n", gif->frame_count);
|
||||
fprintf(fh, "# frame_count_partial %u \n", gif->frame_count_partial);
|
||||
fprintf(fh, "# loop_count %u \n", gif->loop_count);
|
||||
fprintf(fh, "%u %u 256\n", gif->width, gif->height * gif->frame_count);
|
||||
}
|
||||
|
||||
/* decode the frames */
|
||||
for (i = 0; i != gif->frame_count; i++) {
|
||||
unsigned int row, col;
|
||||
unsigned char *image;
|
||||
|
||||
code = gif_decode_frame(gif, i);
|
||||
if (code != GIF_OK)
|
||||
warning("gif_decode_frame", code);
|
||||
|
||||
if (!no_write) {
|
||||
fprintf(fh, "# frame %u:\n", i);
|
||||
image = (unsigned char *) gif->frame_image;
|
||||
for (row = 0; row != gif->height; row++) {
|
||||
for (col = 0; col != gif->width; col++) {
|
||||
size_t z = (row * gif->width + col) * 4;
|
||||
fprintf(fh, "%u %u %u ",
|
||||
(unsigned char) image[z],
|
||||
(unsigned char) image[z + 1],
|
||||
(unsigned char) image[z + 2]);
|
||||
}
|
||||
fprintf(fh, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
gif_bitmap_callback_vt bitmap_callbacks = {
|
||||
bitmap_create,
|
||||
bitmap_destroy,
|
||||
bitmap_get_buffer,
|
||||
bitmap_set_opaque,
|
||||
bitmap_test_opaque,
|
||||
bitmap_modified
|
||||
};
|
||||
gif_animation gif;
|
||||
size_t size;
|
||||
gif_result code;
|
||||
unsigned char *data;
|
||||
FILE *outf = stdout;
|
||||
bool no_write = false;
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s image.gif [out]\n", argv[0]);
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "If [out] is NOWRITE, the gif will be docoded "
|
||||
"but not output.\n");
|
||||
fprintf(stderr, "Otherwise [out] is an output filename.\n");
|
||||
fprintf(stderr, "When [out] is unset, output is to stdout.\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argc > 2) {
|
||||
if (strcmp(argv[2], "NOWRITE") == 0) {
|
||||
no_write = true;
|
||||
} else {
|
||||
outf = fopen(argv[2], "w+");
|
||||
if (outf == NULL) {
|
||||
fprintf(stderr, "Unable to open %s for writing\n", argv[2]);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* create our gif animation */
|
||||
gif_create(&gif, &bitmap_callbacks);
|
||||
|
||||
/* load file into memory */
|
||||
data = load_file(argv[1], &size);
|
||||
|
||||
/* begin decoding */
|
||||
do {
|
||||
code = gif_initialise(&gif, size, data);
|
||||
if (code != GIF_OK && code != GIF_WORKING) {
|
||||
warning("gif_initialise", code);
|
||||
gif_finalise(&gif);
|
||||
free(data);
|
||||
return 1;
|
||||
}
|
||||
} while (code != GIF_OK);
|
||||
|
||||
write_ppm(outf, argv[1], &gif, no_write);
|
||||
|
||||
if (argc > 2 && !no_write) {
|
||||
fclose(outf);
|
||||
}
|
||||
|
||||
/* clean up */
|
||||
gif_finalise(&gif);
|
||||
free(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
310
libvips/foreign/libnsgif/test/nsgif.c
Normal file
310
libvips/foreign/libnsgif/test/nsgif.c
Normal file
@ -0,0 +1,310 @@
|
||||
/*
|
||||
* Copyright 2008 Sean Fox <dyntryx@gmail.com>
|
||||
* Copyright 2008 James Bursa <james@netsurf-browser.org>
|
||||
* Copyright 2022 Michael Drake <tlsa@netsurf-browser.org>
|
||||
*
|
||||
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
|
||||
* Licenced under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "../include/nsgif.h"
|
||||
|
||||
#include "cli.h"
|
||||
#include "cli.c"
|
||||
|
||||
#define BYTES_PER_PIXEL 4
|
||||
|
||||
static struct nsgif_options {
|
||||
const char *file;
|
||||
const char *ppm;
|
||||
uint64_t loops;
|
||||
bool info;
|
||||
} nsgif_options;
|
||||
|
||||
static const struct cli_table_entry cli_entries[] = {
|
||||
{
|
||||
.s = 'm',
|
||||
.l = "ppm",
|
||||
.t = CLI_STRING,
|
||||
.v.s = &nsgif_options.ppm,
|
||||
.d = "Convert frames to PPM image at given path."
|
||||
},
|
||||
{
|
||||
.s = 'i',
|
||||
.l = "info",
|
||||
.t = CLI_BOOL,
|
||||
.v.b = &nsgif_options.info,
|
||||
.d = "Dump GIF info to stdout."
|
||||
},
|
||||
{
|
||||
.s = 'l',
|
||||
.l = "loops",
|
||||
.t = CLI_UINT,
|
||||
.v.u = &nsgif_options.loops,
|
||||
.d = "Loop through decoding all frames N times. "
|
||||
"The default is 1."
|
||||
},
|
||||
{
|
||||
.p = true,
|
||||
.l = "FILE",
|
||||
.t = CLI_STRING,
|
||||
.v.s = &nsgif_options.file,
|
||||
.d = "Path to GIF file to load."
|
||||
},
|
||||
};
|
||||
|
||||
const struct cli_table cli = {
|
||||
.entries = cli_entries,
|
||||
.count = (sizeof(cli_entries))/(sizeof(*cli_entries)),
|
||||
.min_positional = 1,
|
||||
};
|
||||
|
||||
static void *bitmap_create(int width, int height)
|
||||
{
|
||||
/* Ensure a stupidly large bitmap is not created */
|
||||
if (width > 4096 || height > 4096) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return calloc(width * height, BYTES_PER_PIXEL);
|
||||
}
|
||||
|
||||
static unsigned char *bitmap_get_buffer(void *bitmap)
|
||||
{
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
static void bitmap_destroy(void *bitmap)
|
||||
{
|
||||
free(bitmap);
|
||||
}
|
||||
|
||||
static uint8_t *load_file(const char *path, size_t *data_size)
|
||||
{
|
||||
FILE *fd;
|
||||
struct stat sb;
|
||||
unsigned char *buffer;
|
||||
size_t size;
|
||||
size_t n;
|
||||
|
||||
fd = fopen(path, "rb");
|
||||
if (!fd) {
|
||||
perror(path);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (stat(path, &sb)) {
|
||||
perror(path);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
size = sb.st_size;
|
||||
|
||||
buffer = malloc(size);
|
||||
if (!buffer) {
|
||||
fprintf(stderr, "Unable to allocate %lld bytes\n",
|
||||
(long long) size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
n = fread(buffer, 1, size, fd);
|
||||
if (n != size) {
|
||||
perror(path);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
fclose(fd);
|
||||
|
||||
*data_size = size;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void warning(const char *context, nsgif_error err)
|
||||
{
|
||||
fprintf(stderr, "%s failed: %s\n",
|
||||
context, nsgif_strerror(err));
|
||||
}
|
||||
|
||||
static void print_gif_info(const nsgif_info_t *info)
|
||||
{
|
||||
fprintf(stdout, "gif:\n");
|
||||
fprintf(stdout, " width: %"PRIu32"\n", info->width);
|
||||
fprintf(stdout, " height: %"PRIu32"\n", info->height);
|
||||
fprintf(stdout, " max-loops: %"PRIu32"\n", info->loop_max);
|
||||
fprintf(stdout, " frame-count: %"PRIu32"\n", info->frame_count);
|
||||
fprintf(stdout, " background:\n");
|
||||
fprintf(stdout, " red: 0x%"PRIx8"\n", info->background[0]);
|
||||
fprintf(stdout, " green: 0x%"PRIx8"\n", info->background[1]);
|
||||
fprintf(stdout, " blue: 0x%"PRIx8"\n", info->background[2]);
|
||||
fprintf(stdout, " frames:\n");
|
||||
}
|
||||
|
||||
static void print_gif_frame_info(const nsgif_frame_info_t *info)
|
||||
{
|
||||
const char *disposal = nsgif_str_disposal(info->disposal);
|
||||
|
||||
fprintf(stdout, " - disposal-method: %s\n", disposal);
|
||||
fprintf(stdout, " transparency: %s\n", info->transparency ? "yes" : "no");
|
||||
fprintf(stdout, " display: %s\n", info->display ? "yes" : "no");
|
||||
fprintf(stdout, " delay: %"PRIu32"\n", info->delay);
|
||||
fprintf(stdout, " rect:\n");
|
||||
fprintf(stdout, " x: %"PRIu32"\n", info->rect.x0);
|
||||
fprintf(stdout, " y: %"PRIu32"\n", info->rect.y0);
|
||||
fprintf(stdout, " w: %"PRIu32"\n", info->rect.x1 - info->rect.x0);
|
||||
fprintf(stdout, " h: %"PRIu32"\n", info->rect.y1 - info->rect.y0);
|
||||
}
|
||||
|
||||
static void decode(FILE* ppm, const char *name, nsgif_t *gif)
|
||||
{
|
||||
nsgif_error err;
|
||||
uint32_t frame_prev = 0;
|
||||
const nsgif_info_t *info;
|
||||
|
||||
info = nsgif_get_info(gif);
|
||||
|
||||
if (ppm != NULL) {
|
||||
fprintf(ppm, "P3\n");
|
||||
fprintf(ppm, "# %s\n", name);
|
||||
fprintf(ppm, "# width %u \n", info->width);
|
||||
fprintf(ppm, "# height %u \n", info->height);
|
||||
fprintf(ppm, "# frame_count %u \n", info->frame_count);
|
||||
fprintf(ppm, "# loop_max %u \n", info->loop_max);
|
||||
fprintf(ppm, "%u %u 256\n", info->width,
|
||||
info->height * info->frame_count);
|
||||
}
|
||||
|
||||
if (nsgif_options.info == true) {
|
||||
print_gif_info(info);
|
||||
}
|
||||
|
||||
/* decode the frames */
|
||||
while (true) {
|
||||
nsgif_bitmap_t *bitmap;
|
||||
const uint8_t *image;
|
||||
uint32_t frame_new;
|
||||
uint32_t delay_cs;
|
||||
nsgif_rect_t area;
|
||||
|
||||
err = nsgif_frame_prepare(gif, &area,
|
||||
&delay_cs, &frame_new);
|
||||
if (err != NSGIF_OK) {
|
||||
warning("nsgif_frame_prepare", err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame_new < frame_prev) {
|
||||
/* Must be an animation that loops. We only care about
|
||||
* decoding each frame once in this utility. */
|
||||
return;
|
||||
}
|
||||
frame_prev = frame_new;
|
||||
|
||||
err = nsgif_frame_decode(gif, frame_new, &bitmap);
|
||||
if (err != NSGIF_OK) {
|
||||
warning("nsgif_decode_frame", err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nsgif_options.info == true) {
|
||||
const nsgif_frame_info_t *f_info;
|
||||
|
||||
f_info = nsgif_get_frame_info(gif, frame_new);
|
||||
assert(f_info != NULL);
|
||||
print_gif_frame_info(f_info);
|
||||
}
|
||||
|
||||
if (ppm != NULL) {
|
||||
fprintf(ppm, "# frame %u:\n", frame_new);
|
||||
image = (const uint8_t *) bitmap;
|
||||
for (uint32_t y = 0; y != info->height; y++) {
|
||||
for (uint32_t x = 0; x != info->width; x++) {
|
||||
size_t z = (y * info->width + x) * 4;
|
||||
fprintf(ppm, "%u %u %u ",
|
||||
image[z],
|
||||
image[z + 1],
|
||||
image[z + 2]);
|
||||
}
|
||||
fprintf(ppm, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (delay_cs == NSGIF_INFINITE) {
|
||||
/** This frame is the last. */
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const nsgif_bitmap_cb_vt bitmap_callbacks = {
|
||||
.create = bitmap_create,
|
||||
.destroy = bitmap_destroy,
|
||||
.get_buffer = bitmap_get_buffer,
|
||||
};
|
||||
size_t size;
|
||||
nsgif_t *gif;
|
||||
uint8_t *data;
|
||||
nsgif_error err;
|
||||
FILE *ppm = NULL;
|
||||
|
||||
/* Override default options with any command line args */
|
||||
if (!cli_parse(&cli, argc, (void *)argv)) {
|
||||
cli_help(&cli, argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (nsgif_options.ppm != NULL) {
|
||||
ppm = fopen(nsgif_options.ppm, "w+");
|
||||
if (ppm == NULL) {
|
||||
fprintf(stderr, "Unable to open %s for writing\n",
|
||||
nsgif_options.ppm);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/* create our gif animation */
|
||||
err = nsgif_create(&bitmap_callbacks, &gif);
|
||||
if (err != NSGIF_OK) {
|
||||
warning("nsgif_create", err);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* load file into memory */
|
||||
data = load_file(nsgif_options.file, &size);
|
||||
|
||||
/* Scan the raw data */
|
||||
err = nsgif_data_scan(gif, size, data);
|
||||
if (err != NSGIF_OK) {
|
||||
warning("nsgif_data_scan", err);
|
||||
nsgif_destroy(gif);
|
||||
free(data);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (nsgif_options.loops == 0) {
|
||||
nsgif_options.loops = 1;
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < nsgif_options.loops; i++) {
|
||||
decode((i == 0) ? ppm : NULL, nsgif_options.file, gif);
|
||||
}
|
||||
|
||||
if (ppm != NULL) {
|
||||
fclose(ppm);
|
||||
}
|
||||
|
||||
/* clean up */
|
||||
nsgif_destroy(gif);
|
||||
free(data);
|
||||
|
||||
return 0;
|
||||
}
|
@ -7,9 +7,15 @@ set -e
|
||||
git clone git://git.netsurf-browser.org/libnsgif.git
|
||||
|
||||
echo copying out source files ...
|
||||
cp libnsgif/src/libnsgif.c .
|
||||
cp libnsgif/include/libnsgif.h .
|
||||
|
||||
cp libnsgif/README.md README-ns.md
|
||||
|
||||
cp libnsgif/include/nsgif.h .
|
||||
cp libnsgif/src/lzw.[ch] .
|
||||
cp libnsgif/src/gif.c .
|
||||
|
||||
cp libnsgif/test/cli.[ch] test/
|
||||
cp libnsgif/test/nsgif.c test/
|
||||
|
||||
if [ -d "patches" ]
|
||||
then
|
||||
|
@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003 James Bursa <bursa@users.sourceforge.net>
|
||||
* Copyright 2004 John Tytgat <John.Tytgat@aaug.net>
|
||||
*
|
||||
* This file is part of NetSurf, http://www.netsurf-browser.org/
|
||||
* Licenced under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef _LIBNSGIF_LOG_H_
|
||||
#define _LIBNSGIF_LOG_H_
|
||||
|
||||
#ifdef NDEBUG
|
||||
# define LOG(x) ((void) 0)
|
||||
#else
|
||||
# define LOG(x) do { fprintf(stderr, x), fputc('\n', stderr); } while (0)
|
||||
#endif /* NDEBUG */
|
||||
|
||||
#endif /* _LIBNSGIF_LOG_H_ */
|
@ -71,7 +71,7 @@
|
||||
|
||||
#ifdef HAVE_NSGIF
|
||||
|
||||
#include <libnsgif/libnsgif.h>
|
||||
#include <libnsgif/nsgif.h>
|
||||
|
||||
#define VIPS_TYPE_FOREIGN_LOAD_GIF (vips_foreign_load_nsgif_get_type())
|
||||
#define VIPS_FOREIGN_LOAD_GIF( obj ) \
|
||||
@ -105,19 +105,19 @@ typedef struct _VipsForeignLoadNsgif {
|
||||
|
||||
/* The animation created by libnsgif.
|
||||
*/
|
||||
gif_animation *anim;
|
||||
nsgif_t *anim;
|
||||
|
||||
/* The data/size pair we pass to libnsgif.
|
||||
*/
|
||||
unsigned char *data;
|
||||
size_t size;
|
||||
|
||||
/* The frame_count, after we have removed undisplayable frames.
|
||||
/* Information about the current GIF.
|
||||
*/
|
||||
int frame_count_displayable;
|
||||
const nsgif_info_t *info;
|
||||
|
||||
/* Delays between frames (in milliseconds). Array of length
|
||||
* @frame_count_displayable.
|
||||
* @info->frame_count.
|
||||
*/
|
||||
int *delay;
|
||||
|
||||
@ -137,40 +137,13 @@ G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadNsgif, vips_foreign_load_nsgif,
|
||||
VIPS_TYPE_FOREIGN_LOAD );
|
||||
|
||||
static const char *
|
||||
vips_foreign_load_nsgif_errstr( gif_result result )
|
||||
vips_foreign_load_nsgif_errstr( nsgif_error result )
|
||||
{
|
||||
switch( result ) {
|
||||
case GIF_WORKING:
|
||||
return( _( "Working" ) );
|
||||
|
||||
case GIF_OK:
|
||||
return( _( "OK" ) );
|
||||
|
||||
case GIF_FRAME_DATA_ERROR:
|
||||
return( _( "GIF frame data error" ) );
|
||||
|
||||
case GIF_INSUFFICIENT_DATA:
|
||||
return( _( "Incomplete data" ) );
|
||||
|
||||
case GIF_DATA_ERROR:
|
||||
return( _( "GIF header data error" ) );
|
||||
|
||||
case GIF_INSUFFICIENT_MEMORY:
|
||||
return( _( "Insuficient memory to process" ) );
|
||||
|
||||
case GIF_FRAME_NO_DISPLAY:
|
||||
return( _( "No display" ) );
|
||||
|
||||
case GIF_END_OF_FRAME:
|
||||
return( _( "At end of frame" ) );
|
||||
|
||||
default:
|
||||
return( _( "Unknown error" ) );
|
||||
}
|
||||
return nsgif_strerror( result );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_load_nsgif_error( VipsForeignLoadNsgif *gif, gif_result result )
|
||||
vips_foreign_load_nsgif_error( VipsForeignLoadNsgif *gif, nsgif_error result )
|
||||
{
|
||||
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
|
||||
|
||||
@ -186,8 +159,7 @@ vips_foreign_load_nsgif_dispose( GObject *gobject )
|
||||
VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_dispose:\n" );
|
||||
|
||||
if( gif->anim ) {
|
||||
gif_finalise( gif->anim );
|
||||
VIPS_FREE( gif->anim );
|
||||
nsgif_destroy( gif->anim );
|
||||
}
|
||||
VIPS_UNREF( gif->source );
|
||||
VIPS_FREE( gif->delay );
|
||||
@ -224,60 +196,45 @@ vips_foreign_load_nsgif_is_a_source( VipsSource *source )
|
||||
}
|
||||
|
||||
#ifdef VERBOSE
|
||||
static const char *
|
||||
dispose_name( int restore )
|
||||
{
|
||||
switch( restore ) {
|
||||
case 1: return( "combine" );
|
||||
case 2: return( "clear" );
|
||||
case 3: return( "restore" );
|
||||
case 4: return( "quirk restore" );
|
||||
default: return( "none" );
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
print_frame( gif_frame *frame )
|
||||
print_frame( const nsgif_frame_info_t *frame )
|
||||
{
|
||||
if ( frame == NULL )
|
||||
return;
|
||||
|
||||
printf( "frame:\n" );
|
||||
printf( " display = %d\n", frame->display );
|
||||
printf( " frame_delay = %d\n", frame->frame_delay );
|
||||
printf( " virgin = %d\n", frame->virgin );
|
||||
printf( " opaque = %d\n", frame->opaque );
|
||||
printf( " redraw_required = %d\n", frame->redraw_required );
|
||||
printf( " disposal_method = %d (%s)\n",
|
||||
frame->disposal_method,
|
||||
dispose_name( frame->disposal_method ) );
|
||||
printf( " delay = %d\n", frame->delay );
|
||||
// printf( " opaque = %d\n", frame->opaque );
|
||||
printf( " disposal = %d (%s)\n",
|
||||
frame->disposal, nsgif_str_disposal( frame->disposal ) );
|
||||
printf( " transparency = %d\n", frame->transparency );
|
||||
printf( " transparency_index = %d\n", frame->transparency_index );
|
||||
printf( " redraw_x = %d\n", frame->redraw_x );
|
||||
printf( " redraw_y = %d\n", frame->redraw_y );
|
||||
printf( " redraw_width = %d\n", frame->redraw_width );
|
||||
printf( " redraw_height = %d\n", frame->redraw_height );
|
||||
// printf( " transparency_index = %d\n", frame->transparency_index );
|
||||
printf( " rect.x0 = %u\n", frame->rect.x0 );
|
||||
printf( " rect.y0 = %u\n", frame->rect.y0 );
|
||||
printf( " rect.x1 = %u\n", frame->rect.x1 );
|
||||
printf( " rect.y1 = %u\n", frame->rect.y1 );
|
||||
}
|
||||
|
||||
static void
|
||||
print_animation( gif_animation *anim )
|
||||
print_animation( nsgif_t *anim, const nsgif_info_t *info )
|
||||
{
|
||||
int i;
|
||||
|
||||
printf( "animation:\n" );
|
||||
printf( " width = %d\n", anim->width );
|
||||
printf( " height = %d\n", anim->height );
|
||||
printf( " frame_count = %d\n", anim->frame_count );
|
||||
printf( " frame_count_partial = %d\n", anim->frame_count_partial );
|
||||
printf( " decoded_frame = %d\n", anim->decoded_frame );
|
||||
printf( " frame_image = %p\n", anim->frame_image );
|
||||
printf( " loop_count = %d\n", anim->loop_count );
|
||||
printf( " frame_holders = %d\n", anim->frame_holders );
|
||||
printf( " colour_table_size = %d\n", anim->colour_table_size );
|
||||
printf( " global_colours = %d\n", anim->global_colours );
|
||||
printf( " global_colour_table = %p\n", anim->global_colour_table );
|
||||
printf( " local_colour_table = %p\n", anim->local_colour_table );
|
||||
printf( " width = %d\n", info->width );
|
||||
printf( " height = %d\n", info->height );
|
||||
printf( " frame_count = %d\n", info->frame_count );
|
||||
printf( " loop_count = %d\n", info->loop_count );
|
||||
// printf( " colour_table_size = %d\n", anim->colour_table_size );
|
||||
// printf( " global_colours = %d\n", anim->global_colours );
|
||||
// printf( " global_colour_table = %p\n", anim->global_colour_table );
|
||||
// printf( " local_colour_table = %p\n", anim->local_colour_table );
|
||||
|
||||
for( i = 0; i < anim->frame_holders; i++ ) {
|
||||
for( i = 0; i < info->frame_count; i++ ) {
|
||||
printf( "%d ", i );
|
||||
print_frame( &anim->frames[i] );
|
||||
print_frame( nsgif_get_frame_info( anim, i ) );
|
||||
}
|
||||
}
|
||||
#endif /*VERBOSE*/
|
||||
@ -286,10 +243,12 @@ static int
|
||||
vips_foreign_load_nsgif_set_header( VipsForeignLoadNsgif *gif,
|
||||
VipsImage *image )
|
||||
{
|
||||
double array[3];
|
||||
|
||||
VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_set_header:\n" );
|
||||
|
||||
vips_image_init_fields( image,
|
||||
gif->anim->width, gif->anim->height * gif->n,
|
||||
gif->info->width, gif->info->height * gif->n,
|
||||
gif->has_transparency ? 4 : 3,
|
||||
VIPS_FORMAT_UCHAR, VIPS_CODING_NONE,
|
||||
VIPS_INTERPRETATION_sRGB, 1.0, 1.0 );
|
||||
@ -300,29 +259,19 @@ vips_foreign_load_nsgif_set_header( VipsForeignLoadNsgif *gif,
|
||||
*/
|
||||
if( gif->n > 1 )
|
||||
vips_image_set_int( image,
|
||||
VIPS_META_PAGE_HEIGHT, gif->anim->height );
|
||||
VIPS_META_PAGE_HEIGHT, gif->info->height );
|
||||
vips_image_set_int( image, VIPS_META_N_PAGES,
|
||||
gif->frame_count_displayable );
|
||||
vips_image_set_int( image, "loop", gif->anim->loop_count );
|
||||
gif->info->frame_count );
|
||||
vips_image_set_int( image, "loop", gif->info->loop_max );
|
||||
|
||||
vips_image_set_array_int( image, "delay",
|
||||
gif->delay, gif->frame_count_displayable );
|
||||
gif->delay, gif->info->frame_count );
|
||||
|
||||
if( gif->anim->global_colours &&
|
||||
gif->anim->global_colour_table &&
|
||||
gif->anim->bg_index >= 0 &&
|
||||
gif->anim->bg_index < gif->anim->colour_table_size ) {
|
||||
int index = gif->anim->bg_index;
|
||||
unsigned char *entry = (unsigned char *)
|
||||
&gif->anim->global_colour_table[index];
|
||||
array[0] = gif->info->background[0];
|
||||
array[1] = gif->info->background[1];
|
||||
array[2] = gif->info->background[2];
|
||||
|
||||
double array[3];
|
||||
|
||||
array[0] = entry[0];
|
||||
array[1] = entry[1];
|
||||
array[2] = entry[2];
|
||||
|
||||
vips_image_set_array_double( image, "background", array, 3 );
|
||||
}
|
||||
vips_image_set_array_double( image, "background", array, 3 );
|
||||
|
||||
VIPS_SETSTR( image->filename,
|
||||
vips_connection_filename( VIPS_CONNECTION( gif->source ) ) );
|
||||
@ -333,8 +282,8 @@ vips_foreign_load_nsgif_set_header( VipsForeignLoadNsgif *gif,
|
||||
* but we want to keep the old behavior untouched!
|
||||
*/
|
||||
vips_image_set_int( image,
|
||||
"gif-loop", gif->anim->loop_count == 0 ?
|
||||
0 : gif->anim->loop_count - 1 );
|
||||
"gif-loop", gif->info->loop_max == 0 ?
|
||||
0 : gif->info->loop_max - 1 );
|
||||
|
||||
/* The deprecated gif-delay field is in centiseconds.
|
||||
*/
|
||||
@ -359,7 +308,7 @@ vips_foreign_load_nsgif_header( VipsForeignLoad *load )
|
||||
|
||||
const void *data;
|
||||
size_t size;
|
||||
gif_result result;
|
||||
nsgif_error result;
|
||||
int i;
|
||||
|
||||
VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_header:\n" );
|
||||
@ -371,55 +320,40 @@ vips_foreign_load_nsgif_header( VipsForeignLoad *load )
|
||||
return( -1 );
|
||||
vips_source_minimise( gif->source );
|
||||
|
||||
result = gif_initialise( gif->anim, size, (void *) data );
|
||||
VIPS_DEBUG_MSG( "gif_initialise() = %d\n", result );
|
||||
result = nsgif_data_scan( gif->anim, size, (void *) data );
|
||||
VIPS_DEBUG_MSG( "nsgif_data_scan() = %s\n", nsgif_strerror( result ) );
|
||||
gif->info = nsgif_get_info(gif->anim);
|
||||
#ifdef VERBOSE
|
||||
print_animation( gif->anim );
|
||||
print_animation( gif->anim, gif->info );
|
||||
#endif /*VERBOSE*/
|
||||
if( result != GIF_OK &&
|
||||
result != GIF_WORKING &&
|
||||
result != GIF_INSUFFICIENT_FRAME_DATA ) {
|
||||
if( result != NSGIF_OK ) {
|
||||
vips_foreign_load_nsgif_error( gif, result );
|
||||
return( -1 );
|
||||
}
|
||||
else if( result == GIF_INSUFFICIENT_FRAME_DATA &&
|
||||
load->fail_on >= VIPS_FAIL_ON_TRUNCATED ) {
|
||||
vips_error( class->nickname, "%s", _( "truncated GIF" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Many GIFs have dead frames at the end. Remove these from our count.
|
||||
*/
|
||||
for( i = gif->anim->frame_count - 1;
|
||||
i >= 0 && !gif->anim->frames[i].display; i-- )
|
||||
;
|
||||
gif->frame_count_displayable = i + 1;
|
||||
#ifdef VERBOSE
|
||||
if( gif->frame_count_displayable != gif->anim->frame_count )
|
||||
printf( "vips_foreign_load_nsgif_open: "
|
||||
"removed %d undisplayable frames\n",
|
||||
gif->anim->frame_count - gif->frame_count_displayable );
|
||||
#endif /*VERBOSE*/
|
||||
|
||||
if( !gif->frame_count_displayable ) {
|
||||
if( !gif->info->frame_count ) {
|
||||
vips_error( class->nickname, "%s", _( "no frames in GIF" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Check for any transparency.
|
||||
*/
|
||||
for( i = 0; i < gif->frame_count_displayable; i++ )
|
||||
if( gif->anim->frames[i].transparency ) {
|
||||
for( i = 0; i < gif->info->frame_count; i++ ) {
|
||||
const nsgif_frame_info_t *frame_info;
|
||||
|
||||
frame_info = nsgif_get_frame_info( gif->anim, i );
|
||||
if( frame_info != NULL && frame_info->transparency ) {
|
||||
gif->has_transparency = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( gif->n == -1 )
|
||||
gif->n = gif->frame_count_displayable - gif->page;
|
||||
gif->n = gif->info->frame_count - gif->page;
|
||||
|
||||
if( gif->page < 0 ||
|
||||
gif->n <= 0 ||
|
||||
gif->page + gif->n > gif->frame_count_displayable ) {
|
||||
gif->page + gif->n > gif->info->frame_count ) {
|
||||
vips_error( class->nickname, "%s", _( "bad page number" ) );
|
||||
return( -1 );
|
||||
}
|
||||
@ -428,12 +362,20 @@ vips_foreign_load_nsgif_header( VipsForeignLoad *load )
|
||||
*/
|
||||
VIPS_FREE( gif->delay );
|
||||
if( !(gif->delay = VIPS_ARRAY( NULL,
|
||||
gif->frame_count_displayable, int )) )
|
||||
gif->info->frame_count, int )) )
|
||||
return( -1 );
|
||||
for( i = 0; i < gif->frame_count_displayable; i++ )
|
||||
gif->delay[i] = 10 * gif->anim->frames[i].frame_delay;
|
||||
for( i = 0; i < gif->info->frame_count; i++ ) {
|
||||
const nsgif_frame_info_t *frame_info;
|
||||
|
||||
gif->gif_delay = gif->anim->frames[0].frame_delay;
|
||||
frame_info = nsgif_get_frame_info( gif->anim, i );
|
||||
if ( frame_info == NULL ) {
|
||||
vips_error( class->nickname, "%s", _( "bad frame" ) );
|
||||
return( -1 );
|
||||
}
|
||||
gif->delay[i] = 10 * frame_info->delay;
|
||||
}
|
||||
|
||||
gif->gif_delay = gif->delay[0] / 10;
|
||||
|
||||
vips_foreign_load_nsgif_set_header( gif, load->out );
|
||||
|
||||
@ -444,7 +386,9 @@ static int
|
||||
vips_foreign_load_nsgif_generate( VipsRegion *or,
|
||||
void *seq, void *a, void *b, gboolean *stop )
|
||||
{
|
||||
VipsRect *r = &or->valid;
|
||||
int prev_page = -1;
|
||||
nsgif_bitmap_t *bitmap;
|
||||
VipsRect *r = &or->valid;
|
||||
VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) a;
|
||||
|
||||
int y;
|
||||
@ -457,30 +401,30 @@ vips_foreign_load_nsgif_generate( VipsRegion *or,
|
||||
for( y = 0; y < r->height; y++ ) {
|
||||
/* The page for this output line, and the line number in page.
|
||||
*/
|
||||
int page = (r->top + y) / gif->anim->height + gif->page;
|
||||
int line = (r->top + y) % gif->anim->height;
|
||||
int page = (r->top + y) / gif->info->height + gif->page;
|
||||
int line = (r->top + y) % gif->info->height;
|
||||
|
||||
gif_result result;
|
||||
nsgif_error result;
|
||||
VipsPel *p, *q;
|
||||
|
||||
g_assert( line >= 0 && line < gif->anim->height );
|
||||
g_assert( page >= 0 && page < gif->frame_count_displayable );
|
||||
g_assert( line >= 0 && line < gif->info->height );
|
||||
g_assert( page >= 0 && page < gif->info->frame_count );
|
||||
|
||||
if( gif->anim->decoded_frame != page ) {
|
||||
result = gif_decode_frame( gif->anim, page );
|
||||
VIPS_DEBUG_MSG( " gif_decode_frame(%d) = %d\n",
|
||||
if( prev_page != page ) {
|
||||
result = nsgif_frame_decode( gif->anim, page, &bitmap );
|
||||
VIPS_DEBUG_MSG( " nsgif_frame_decode(%d) = %d\n",
|
||||
page, result );
|
||||
if( result != GIF_OK ) {
|
||||
vips_foreign_load_nsgif_error( gif, result );
|
||||
if( result != NSGIF_OK ) {
|
||||
vips_foreign_load_nsgif_error( gif, result );
|
||||
return( -1 );
|
||||
}
|
||||
#ifdef VERBOSE
|
||||
print_animation( gif->anim );
|
||||
#endif /*VERBOSE*/
|
||||
prev_page = page;
|
||||
}
|
||||
|
||||
p = gif->anim->frame_image +
|
||||
line * gif->anim->width * sizeof( int );
|
||||
p = bitmap + line * gif->info->width * sizeof( int );
|
||||
q = VIPS_REGION_ADDR( or, 0, r->top + y );
|
||||
if( gif->has_transparency )
|
||||
memcpy( q, p, VIPS_REGION_SIZEOF_LINE( or ) );
|
||||
@ -587,23 +531,6 @@ vips_foreign_load_nsgif_bitmap_create( int width, int height )
|
||||
return g_malloc0( (gsize) width * height * 4 );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_load_nsgif_bitmap_set_opaque( void *bitmap, bool opaque )
|
||||
{
|
||||
(void) opaque; /* unused */
|
||||
(void) bitmap; /* unused */
|
||||
g_assert( bitmap );
|
||||
}
|
||||
|
||||
static bool
|
||||
vips_foreign_load_nsgif_bitmap_test_opaque( void *bitmap )
|
||||
{
|
||||
(void) bitmap; /* unused */
|
||||
g_assert( bitmap );
|
||||
|
||||
return( false );
|
||||
}
|
||||
|
||||
static unsigned char *
|
||||
vips_foreign_load_nsgif_bitmap_get_buffer( void *bitmap )
|
||||
{
|
||||
@ -619,29 +546,24 @@ vips_foreign_load_nsgif_bitmap_destroy( void *bitmap )
|
||||
g_free( bitmap );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_load_nsgif_bitmap_modified( void *bitmap )
|
||||
{
|
||||
(void) bitmap; /* unused */
|
||||
g_assert( bitmap );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static gif_bitmap_callback_vt vips_foreign_load_nsgif_bitmap_callbacks = {
|
||||
static nsgif_bitmap_cb_vt vips_foreign_load_nsgif_bitmap_callbacks = {
|
||||
vips_foreign_load_nsgif_bitmap_create,
|
||||
vips_foreign_load_nsgif_bitmap_destroy,
|
||||
vips_foreign_load_nsgif_bitmap_get_buffer,
|
||||
vips_foreign_load_nsgif_bitmap_set_opaque,
|
||||
vips_foreign_load_nsgif_bitmap_test_opaque,
|
||||
vips_foreign_load_nsgif_bitmap_modified
|
||||
};
|
||||
|
||||
static void
|
||||
vips_foreign_load_nsgif_init( VipsForeignLoadNsgif *gif )
|
||||
{
|
||||
gif->anim = g_new0( gif_animation, 1 );
|
||||
gif_create( gif->anim, &vips_foreign_load_nsgif_bitmap_callbacks );
|
||||
nsgif_error result = nsgif_create(
|
||||
&vips_foreign_load_nsgif_bitmap_callbacks,
|
||||
&gif->anim );
|
||||
if (result != NSGIF_OK) {
|
||||
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
|
||||
vips_error( class->nickname, "%s",
|
||||
nsgif_strerror( result ) );
|
||||
return;
|
||||
}
|
||||
gif->n = 1;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user