123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- // ICO loader
- //
- // See: https://en.wikipedia.org/wiki/ICO_(file_format)
- #include "vips.h"
- #include <stdlib.h>
- #include <stdint.h>
- #include <stdbool.h>
- /**
- * ICO ForeignLoad VIPS class implementation (generic)
- */
- typedef struct _VipsForeignLoadIco {
- VipsForeignLoad parent_object;
- VipsSource *source;
- VipsImage **internal; // internal image
- } VipsForeignLoadIco;
- typedef VipsForeignLoadClass VipsForeignLoadIcoClass;
- G_DEFINE_ABSTRACT_TYPE(VipsForeignLoadIco, vips_foreign_load_ico,
- VIPS_TYPE_FOREIGN_LOAD);
- static void
- vips_foreign_load_ico_dispose(GObject *gobject)
- {
- VipsForeignLoadIco *ico = (VipsForeignLoadIco *) gobject;
- VIPS_UNREF(ico->source);
- G_OBJECT_CLASS(vips_foreign_load_ico_parent_class)->dispose(gobject);
- }
- static int
- vips_foreign_load_ico_build(VipsObject *object)
- {
- VipsForeignLoadIco *ico = (VipsForeignLoadIco *) object;
- return VIPS_OBJECT_CLASS(vips_foreign_load_ico_parent_class)
- ->build(object);
- }
- static VipsForeignFlags
- vips_foreign_load_ico_get_flags(VipsForeignLoad *load)
- {
- return VIPS_FOREIGN_SEQUENTIAL;
- }
- /**
- * Checks if the source is a ICO image
- */
- static gboolean
- vips_foreign_load_ico_source_is_a_source(VipsSource *source)
- {
- // There is no way of detecting ICO files (it has no signature).
- // However, vips requires this method to be present.
- unsigned char *buf = vips_source_sniff(source, 4);
- if (!buf) {
- vips_error("vips_foreign_load_bmp_source_is_a_source", "unable to sniff source");
- return 0;
- }
- return buf[0] == 0 && buf[1] == 0 && buf[2] == 1 && buf[3] == 0;
- }
- /**
- * Checks if the ICO image is a PNG image.
- */
- static bool
- vips_foreign_load_ico_is_png(VipsForeignLoadIco *ico, VipsPel *data, uint32_t data_size)
- {
- // Check if the ICO data is PNG
- // ICO files can contain PNG images, so we need to check the magic bytes
- if (data_size < 8) {
- return false; // Not enough data to be a PNG
- }
- // Check the PNG signature
- return (data[0] == 137 && data[1] == 'P' && data[2] == 'N' &&
- data[3] == 'G' && data[4] == '\r' && data[5] == '\n' &&
- data[6] == 26 && data[7] == '\n');
- }
- void
- vips_foreign_load_ico_free_buffer(
- VipsObject *self,
- gpointer user_data)
- {
- VIPS_FREE(user_data);
- }
- /**
- * Loads the header of the ICO image from the source.
- */
- static int
- vips_foreign_load_ico_header(VipsForeignLoad *load)
- {
- VipsForeignLoadIco *ico = (VipsForeignLoadIco *) load;
- // Rewind the source to the beginning
- if (vips_source_rewind(ico->source))
- return -1;
- ICONDIR_IcoHeader file_header;
- // Read the header
- if (vips_foreign_load_read_full(ico->source, &file_header, sizeof(file_header)) <= 0) {
- vips_error("vips_foreign_load_ico_header", "unable to read file header from the source");
- return -1;
- }
- // Get the image count from the file header
- uint16_t count = GUINT16_FROM_LE(file_header.image_count);
- // Now, let's find the largest image in the set
- ICONDIRENTRY_IcoHeader largest_image_header;
- memset(&largest_image_header, 0, sizeof(largest_image_header));
- for (int i = 0; i < count; i++) {
- ICONDIRENTRY_IcoHeader image_header;
- // Read the next header
- if (vips_foreign_load_read_full(ico->source, &image_header, sizeof(image_header)) <= 0) {
- vips_error("vips_foreign_load_ico_header", "unable to read file header from the source");
- return -1;
- }
- // this image width/height is greater than the largest image width/height
- // or this image width/height is 0 (which means 256)
- bool image_is_larger = ((image_header.width > largest_image_header.width) || (image_header.height > largest_image_header.height) || (image_header.width == 0) || (image_header.height == 0));
- // Update the largest image header
- if (image_is_larger) {
- memcpy(&largest_image_header, &image_header, sizeof(largest_image_header));
- }
- }
- // We failed to find any image which fits
- if (largest_image_header.data_offset == 0) {
- vips_error("vips_foreign_load_ico_header", "ICO file has no image which fits");
- return -1;
- }
- // Let's move to the ico image data offset.
- if (vips_source_seek(ico->source, GUINT32_FROM_LE(largest_image_header.data_offset), SEEK_SET) < 1) {
- vips_error("vips_foreign_load_ico_header", "unable to seek to ICO image data");
- return -1;
- }
- // Read the image into memory (otherwise, it would be too complex to handle). It's fine for ICO:
- // ICO files are usually small, and we can read them into memory without any issues.
- uint32_t data_size = GUINT32_FROM_LE(largest_image_header.data_size);
- // BMP file explicitly excludes BITMAPFILEHEADER, so we need to add it manually. We reserve
- // space for it at the beginning of the data buffer.
- VipsPel *data = (VipsPel *) VIPS_MALLOC(NULL, data_size + BMP_FILE_HEADER_LEN);
- void *actual_data = data + BMP_FILE_HEADER_LEN;
- if (vips_foreign_load_read_full(ico->source, actual_data, data_size) <= 0) {
- vips_error("vips_foreign_load_ico_header", "unable to read ICO image data from the source");
- return -1;
- }
- // Now, let's load the internal image
- ico->internal = (VipsImage **) vips_object_local_array(VIPS_OBJECT(load), 1);
- if (vips_foreign_load_ico_is_png(ico, actual_data, data_size)) {
- if (
- vips_pngload_buffer(
- actual_data, data_size,
- &ico->internal[0],
- "access", VIPS_ACCESS_SEQUENTIAL,
- NULL) < 0) {
- VIPS_FREE(data);
- vips_error("vips_foreign_load_ico_header", "unable to load ICO image as PNG");
- return -1;
- }
- }
- else {
- // Otherwise, we assume it's a BMP image.
- // According to ICO file format, it explicitly excludes BITMAPFILEHEADER (why???),
- // hence, we need to restore it to make bmp loader work.
- // Read num_colors and bpp from the BITMAPINFOHEADER
- uint32_t num_colors = GUINT32_FROM_LE(*(uint32_t *) (actual_data + 32));
- uint16_t bpp = GUINT16_FROM_LE(*(uint16_t *) (actual_data + 14));
- uint32_t pix_offset;
- if ((num_colors == 0) && (bpp <= 8)) {
- // If there are no colors and bpp is <= 8, we assume it's a palette image
- pix_offset = BMP_FILE_HEADER_LEN + BMP_BITMAP_INFO_HEADER_LEN + 4 * (1 << bpp);
- }
- else {
- // Otherwise, we use the number of colors
- pix_offset = BMP_FILE_HEADER_LEN + BMP_BITMAP_INFO_HEADER_LEN + 4 * num_colors;
- }
- // ICO file used to store alpha mask. By historical reasons, height of the ICO bmp
- // is still stored doubled to cover the alpha mask data even if they're zero
- // or not present.
- int32_t height = GINT32_FROM_LE(*(int32_t *) (actual_data + 8));
- height = height / 2;
- // Magic bytes
- data[0] = 'B';
- data[1] = 'M';
- // Size of the BMP file (data size + BMP file header length)
- (*(uint32_t *) (data + 2)) = GUINT32_TO_LE(data_size + BMP_FILE_HEADER_LEN);
- (*(uint32_t *) (data + 6)) = 0; // reserved
- (*(uint32_t *) (data + 10)) = GUINT32_TO_LE(pix_offset); // offset to the pixel data
- (*(int32_t *) (actual_data + 8)) = GINT32_TO_LE(height); // height
- if (
- vips_bmpload_buffer(
- data, data_size + BMP_FILE_HEADER_LEN,
- &ico->internal[0],
- "access", VIPS_ACCESS_SEQUENTIAL,
- NULL) < 0) {
- VIPS_FREE(data);
- vips_error("vips_foreign_load_ico_header", "unable to load ICO image as BMP");
- return -1;
- }
- }
- // It is recommended that we free the buffer in postclose callback
- g_signal_connect(
- ico->internal[0], "postclose",
- G_CALLBACK(vips_foreign_load_ico_free_buffer), data);
- // Check that target dimensions match the source dimensions.
- int lhw = largest_image_header.width == 0 ? 256 : largest_image_header.width;
- int lhh = largest_image_header.height == 0 ? 256 : largest_image_header.height;
- if (vips_image_get_width(ico->internal[0]) != lhw ||
- vips_image_get_height(ico->internal[0]) != lhh) {
- vips_error("vips_foreign_load_ico_header", "ICO image has unexpected dimensions");
- return -1;
- }
- // Copy the image metadata parameters to the load->out image.
- // This should be sufficient, as we do not care much about the rest of the
- // metadata inside .ICO files. At least, at this stage.
- vips_image_init_fields(
- load->out,
- vips_image_get_width(ico->internal[0]),
- vips_image_get_height(ico->internal[0]),
- vips_image_get_bands(ico->internal[0]),
- vips_image_get_format(ico->internal[0]),
- vips_image_get_coding(ico->internal[0]),
- vips_image_get_interpretation(ico->internal[0]),
- vips_image_get_xres(ico->internal[0]),
- vips_image_get_yres(ico->internal[0]));
- vips_source_minimise(ico->source);
- return 0;
- }
- /**
- * Loads a ICO image from the source.
- */
- static int
- vips_foreign_load_ico_load(VipsForeignLoad *load)
- {
- VipsForeignLoadIco *ico = (VipsForeignLoadIco *) load;
- VipsImage *image = ico->internal[0];
- // Just copy the internal image to the output image
- if (vips_image_write(image, load->real)) {
- vips_error("vips_foreign_load_ico_load", "unable to copy ICO image to output");
- return -1;
- }
- return 0;
- }
- static void
- vips_foreign_load_ico_class_init(VipsForeignLoadIcoClass *class)
- {
- GObjectClass *gobject_class = G_OBJECT_CLASS(class);
- VipsObjectClass *object_class = (VipsObjectClass *) class;
- VipsForeignClass *foreign_class = (VipsForeignClass *) class;
- VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
- gobject_class->dispose = vips_foreign_load_ico_dispose;
- gobject_class->set_property = vips_object_set_property;
- gobject_class->get_property = vips_object_get_property;
- object_class->nickname = "icoload_base";
- object_class->description = "load ico image (internal format for video thumbs)";
- object_class->build = vips_foreign_load_ico_build;
- load_class->get_flags = vips_foreign_load_ico_get_flags;
- load_class->header = vips_foreign_load_ico_header;
- load_class->load = vips_foreign_load_ico_load;
- }
- static void
- vips_foreign_load_ico_init(VipsForeignLoadIco *load)
- {
- }
- typedef struct _VipsForeignLoadIcoSource {
- VipsForeignLoadIco parent_object;
- VipsSource *source;
- } VipsForeignLoadIcoSource;
- typedef VipsForeignLoadIcoClass VipsForeignLoadIcoSourceClass;
- G_DEFINE_TYPE(VipsForeignLoadIcoSource, vips_foreign_load_ico_source,
- vips_foreign_load_ico_get_type());
- static int
- vips_foreign_load_ico_source_build(VipsObject *object)
- {
- VipsForeignLoadIco *ico = (VipsForeignLoadIco *) object;
- VipsForeignLoadIcoSource *source =
- (VipsForeignLoadIcoSource *) object;
- if (source->source) {
- ico->source = source->source;
- g_object_ref(ico->source);
- }
- return VIPS_OBJECT_CLASS(vips_foreign_load_ico_source_parent_class)
- ->build(object);
- }
- static void
- vips_foreign_load_ico_source_class_init(
- VipsForeignLoadIcoSourceClass *class)
- {
- GObjectClass *gobject_class = G_OBJECT_CLASS(class);
- VipsObjectClass *object_class = (VipsObjectClass *) class;
- VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class);
- VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
- gobject_class->set_property = vips_object_set_property;
- gobject_class->get_property = vips_object_get_property;
- object_class->nickname = "icoload_source";
- object_class->description = "load image from ico source";
- object_class->build = vips_foreign_load_ico_source_build;
- load_class->is_a_source = vips_foreign_load_ico_source_is_a_source;
- VIPS_ARG_OBJECT(class, "source", 1,
- "Source",
- "Source to load from",
- VIPS_ARGUMENT_REQUIRED_INPUT,
- G_STRUCT_OFFSET(VipsForeignLoadIcoSource, source),
- VIPS_TYPE_SOURCE);
- }
- static void
- vips_foreign_load_ico_source_init(VipsForeignLoadIcoSource *source)
- {
- }
- /**
- * vips_icoload_source:
- * @source: source to load
- * @out: (out): image to write
- * @...: `NULL`-terminated list of optional named arguments
- *
- * Read a RAWRGB-formatted memory block into a VIPS image.
- *
- * Returns: 0 on success, -1 on error.
- */
- int
- vips_icoload_source(VipsSource *source, VipsImage **out, ...)
- {
- va_list ap;
- int result;
- va_start(ap, out);
- result = vips_call_split("icoload_source", ap, source, out);
- va_end(ap);
- return result;
- }
- // wrapper function which hiders varargs (...) from CGo
- int
- vips_icoload_source_go(VipsImgproxySource *source, VipsImage **out)
- {
- return vips_icoload_source(VIPS_SOURCE(source), out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
- }
|