icoload.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. // ICO loader
  2. //
  3. // See: https://en.wikipedia.org/wiki/ICO_(file_format)
  4. #include "vips.h"
  5. #include <stdlib.h>
  6. #include <stdint.h>
  7. #include <stdbool.h>
  8. /**
  9. * ICO ForeignLoad VIPS class implementation (generic)
  10. */
  11. typedef struct _VipsForeignLoadIco {
  12. VipsForeignLoad parent_object;
  13. VipsSource *source;
  14. VipsImage **internal; // internal image
  15. } VipsForeignLoadIco;
  16. typedef VipsForeignLoadClass VipsForeignLoadIcoClass;
  17. G_DEFINE_ABSTRACT_TYPE(VipsForeignLoadIco, vips_foreign_load_ico,
  18. VIPS_TYPE_FOREIGN_LOAD);
  19. static void
  20. vips_foreign_load_ico_dispose(GObject *gobject)
  21. {
  22. VipsForeignLoadIco *ico = (VipsForeignLoadIco *) gobject;
  23. VIPS_UNREF(ico->source);
  24. G_OBJECT_CLASS(vips_foreign_load_ico_parent_class)->dispose(gobject);
  25. }
  26. static int
  27. vips_foreign_load_ico_build(VipsObject *object)
  28. {
  29. VipsForeignLoadIco *ico = (VipsForeignLoadIco *) object;
  30. return VIPS_OBJECT_CLASS(vips_foreign_load_ico_parent_class)
  31. ->build(object);
  32. }
  33. static VipsForeignFlags
  34. vips_foreign_load_ico_get_flags(VipsForeignLoad *load)
  35. {
  36. return VIPS_FOREIGN_SEQUENTIAL;
  37. }
  38. /**
  39. * Checks if the source is a ICO image
  40. */
  41. static gboolean
  42. vips_foreign_load_ico_source_is_a_source(VipsSource *source)
  43. {
  44. // There is no way of detecting ICO files (it has no signature).
  45. // However, vips requires this method to be present.
  46. unsigned char *buf = vips_source_sniff(source, 4);
  47. if (!buf) {
  48. vips_error("vips_foreign_load_bmp_source_is_a_source", "unable to sniff source");
  49. return 0;
  50. }
  51. return buf[0] == 0 && buf[1] == 0 && buf[2] == 1 && buf[3] == 0;
  52. }
  53. /**
  54. * Checks if the ICO image is a PNG image.
  55. */
  56. static bool
  57. vips_foreign_load_ico_is_png(VipsForeignLoadIco *ico, VipsPel *data, uint32_t data_size)
  58. {
  59. // Check if the ICO data is PNG
  60. // ICO files can contain PNG images, so we need to check the magic bytes
  61. if (data_size < 8) {
  62. return false; // Not enough data to be a PNG
  63. }
  64. // Check the PNG signature
  65. return (data[0] == 137 && data[1] == 'P' && data[2] == 'N' &&
  66. data[3] == 'G' && data[4] == '\r' && data[5] == '\n' &&
  67. data[6] == 26 && data[7] == '\n');
  68. }
  69. void
  70. vips_foreign_load_ico_free_buffer(
  71. VipsObject *self,
  72. gpointer user_data)
  73. {
  74. VIPS_FREE(user_data);
  75. }
  76. /**
  77. * Loads the header of the ICO image from the source.
  78. */
  79. static int
  80. vips_foreign_load_ico_header(VipsForeignLoad *load)
  81. {
  82. VipsForeignLoadIco *ico = (VipsForeignLoadIco *) load;
  83. // Rewind the source to the beginning
  84. if (vips_source_rewind(ico->source))
  85. return -1;
  86. ICONDIR_IcoHeader file_header;
  87. // Read the header
  88. if (vips_foreign_load_read_full(ico->source, &file_header, sizeof(file_header)) <= 0) {
  89. vips_error("vips_foreign_load_ico_header", "unable to read file header from the source");
  90. return -1;
  91. }
  92. // Get the image count from the file header
  93. uint16_t count = GUINT16_FROM_LE(file_header.image_count);
  94. // Now, let's find the largest image in the set
  95. ICONDIRENTRY_IcoHeader largest_image_header;
  96. memset(&largest_image_header, 0, sizeof(largest_image_header));
  97. for (int i = 0; i < count; i++) {
  98. ICONDIRENTRY_IcoHeader image_header;
  99. // Read the next header
  100. if (vips_foreign_load_read_full(ico->source, &image_header, sizeof(image_header)) <= 0) {
  101. vips_error("vips_foreign_load_ico_header", "unable to read file header from the source");
  102. return -1;
  103. }
  104. // this image width/height is greater than the largest image width/height
  105. // or this image width/height is 0 (which means 256)
  106. 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));
  107. // Update the largest image header
  108. if (image_is_larger) {
  109. memcpy(&largest_image_header, &image_header, sizeof(largest_image_header));
  110. }
  111. }
  112. // We failed to find any image which fits
  113. if (largest_image_header.data_offset == 0) {
  114. vips_error("vips_foreign_load_ico_header", "ICO file has no image which fits");
  115. return -1;
  116. }
  117. // Let's move to the ico image data offset.
  118. if (vips_source_seek(ico->source, GUINT32_FROM_LE(largest_image_header.data_offset), SEEK_SET) < 1) {
  119. vips_error("vips_foreign_load_ico_header", "unable to seek to ICO image data");
  120. return -1;
  121. }
  122. // Read the image into memory (otherwise, it would be too complex to handle). It's fine for ICO:
  123. // ICO files are usually small, and we can read them into memory without any issues.
  124. uint32_t data_size = GUINT32_FROM_LE(largest_image_header.data_size);
  125. // BMP file explicitly excludes BITMAPFILEHEADER, so we need to add it manually. We reserve
  126. // space for it at the beginning of the data buffer.
  127. VipsPel *data = (VipsPel *) VIPS_MALLOC(NULL, data_size + BMP_FILE_HEADER_LEN);
  128. void *actual_data = data + BMP_FILE_HEADER_LEN;
  129. if (vips_foreign_load_read_full(ico->source, actual_data, data_size) <= 0) {
  130. vips_error("vips_foreign_load_ico_header", "unable to read ICO image data from the source");
  131. return -1;
  132. }
  133. // Now, let's load the internal image
  134. ico->internal = (VipsImage **) vips_object_local_array(VIPS_OBJECT(load), 1);
  135. if (vips_foreign_load_ico_is_png(ico, actual_data, data_size)) {
  136. if (
  137. vips_pngload_buffer(
  138. actual_data, data_size,
  139. &ico->internal[0],
  140. "access", VIPS_ACCESS_SEQUENTIAL,
  141. NULL) < 0) {
  142. VIPS_FREE(data);
  143. vips_error("vips_foreign_load_ico_header", "unable to load ICO image as PNG");
  144. return -1;
  145. }
  146. }
  147. else {
  148. // Otherwise, we assume it's a BMP image.
  149. // According to ICO file format, it explicitly excludes BITMAPFILEHEADER (why???),
  150. // hence, we need to restore it to make bmp loader work.
  151. // Read num_colors and bpp from the BITMAPINFOHEADER
  152. uint32_t num_colors = GUINT32_FROM_LE(*(uint32_t *) (actual_data + 32));
  153. uint16_t bpp = GUINT16_FROM_LE(*(uint16_t *) (actual_data + 14));
  154. uint32_t pix_offset;
  155. if ((num_colors == 0) && (bpp <= 8)) {
  156. // If there are no colors and bpp is <= 8, we assume it's a palette image
  157. pix_offset = BMP_FILE_HEADER_LEN + BMP_BITMAP_INFO_HEADER_LEN + 4 * (1 << bpp);
  158. }
  159. else {
  160. // Otherwise, we use the number of colors
  161. pix_offset = BMP_FILE_HEADER_LEN + BMP_BITMAP_INFO_HEADER_LEN + 4 * num_colors;
  162. }
  163. // ICO file used to store alpha mask. By historical reasons, height of the ICO bmp
  164. // is still stored doubled to cover the alpha mask data even if they're zero
  165. // or not present.
  166. int32_t height = GINT32_FROM_LE(*(int32_t *) (actual_data + 8));
  167. height = height / 2;
  168. // Magic bytes
  169. data[0] = 'B';
  170. data[1] = 'M';
  171. // Size of the BMP file (data size + BMP file header length)
  172. (*(uint32_t *) (data + 2)) = GUINT32_TO_LE(data_size + BMP_FILE_HEADER_LEN);
  173. (*(uint32_t *) (data + 6)) = 0; // reserved
  174. (*(uint32_t *) (data + 10)) = GUINT32_TO_LE(pix_offset); // offset to the pixel data
  175. (*(int32_t *) (actual_data + 8)) = GINT32_TO_LE(height); // height
  176. if (
  177. vips_bmpload_buffer(
  178. data, data_size + BMP_FILE_HEADER_LEN,
  179. &ico->internal[0],
  180. "access", VIPS_ACCESS_SEQUENTIAL,
  181. NULL) < 0) {
  182. VIPS_FREE(data);
  183. vips_error("vips_foreign_load_ico_header", "unable to load ICO image as BMP");
  184. return -1;
  185. }
  186. }
  187. // It is recommended that we free the buffer in postclose callback
  188. g_signal_connect(
  189. ico->internal[0], "postclose",
  190. G_CALLBACK(vips_foreign_load_ico_free_buffer), data);
  191. // Check that target dimensions match the source dimensions.
  192. int lhw = largest_image_header.width == 0 ? 256 : largest_image_header.width;
  193. int lhh = largest_image_header.height == 0 ? 256 : largest_image_header.height;
  194. if (vips_image_get_width(ico->internal[0]) != lhw ||
  195. vips_image_get_height(ico->internal[0]) != lhh) {
  196. vips_error("vips_foreign_load_ico_header", "ICO image has unexpected dimensions");
  197. return -1;
  198. }
  199. // Copy the image metadata parameters to the load->out image.
  200. // This should be sufficient, as we do not care much about the rest of the
  201. // metadata inside .ICO files. At least, at this stage.
  202. vips_image_init_fields(
  203. load->out,
  204. vips_image_get_width(ico->internal[0]),
  205. vips_image_get_height(ico->internal[0]),
  206. vips_image_get_bands(ico->internal[0]),
  207. vips_image_get_format(ico->internal[0]),
  208. vips_image_get_coding(ico->internal[0]),
  209. vips_image_get_interpretation(ico->internal[0]),
  210. vips_image_get_xres(ico->internal[0]),
  211. vips_image_get_yres(ico->internal[0]));
  212. vips_source_minimise(ico->source);
  213. return 0;
  214. }
  215. /**
  216. * Loads a ICO image from the source.
  217. */
  218. static int
  219. vips_foreign_load_ico_load(VipsForeignLoad *load)
  220. {
  221. VipsForeignLoadIco *ico = (VipsForeignLoadIco *) load;
  222. VipsImage *image = ico->internal[0];
  223. // Just copy the internal image to the output image
  224. if (vips_image_write(image, load->real)) {
  225. vips_error("vips_foreign_load_ico_load", "unable to copy ICO image to output");
  226. return -1;
  227. }
  228. return 0;
  229. }
  230. static void
  231. vips_foreign_load_ico_class_init(VipsForeignLoadIcoClass *class)
  232. {
  233. GObjectClass *gobject_class = G_OBJECT_CLASS(class);
  234. VipsObjectClass *object_class = (VipsObjectClass *) class;
  235. VipsForeignClass *foreign_class = (VipsForeignClass *) class;
  236. VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
  237. gobject_class->dispose = vips_foreign_load_ico_dispose;
  238. gobject_class->set_property = vips_object_set_property;
  239. gobject_class->get_property = vips_object_get_property;
  240. object_class->nickname = "icoload_base";
  241. object_class->description = "load ico image (internal format for video thumbs)";
  242. object_class->build = vips_foreign_load_ico_build;
  243. load_class->get_flags = vips_foreign_load_ico_get_flags;
  244. load_class->header = vips_foreign_load_ico_header;
  245. load_class->load = vips_foreign_load_ico_load;
  246. }
  247. static void
  248. vips_foreign_load_ico_init(VipsForeignLoadIco *load)
  249. {
  250. }
  251. typedef struct _VipsForeignLoadIcoSource {
  252. VipsForeignLoadIco parent_object;
  253. VipsSource *source;
  254. } VipsForeignLoadIcoSource;
  255. typedef VipsForeignLoadIcoClass VipsForeignLoadIcoSourceClass;
  256. G_DEFINE_TYPE(VipsForeignLoadIcoSource, vips_foreign_load_ico_source,
  257. vips_foreign_load_ico_get_type());
  258. static int
  259. vips_foreign_load_ico_source_build(VipsObject *object)
  260. {
  261. VipsForeignLoadIco *ico = (VipsForeignLoadIco *) object;
  262. VipsForeignLoadIcoSource *source =
  263. (VipsForeignLoadIcoSource *) object;
  264. if (source->source) {
  265. ico->source = source->source;
  266. g_object_ref(ico->source);
  267. }
  268. return VIPS_OBJECT_CLASS(vips_foreign_load_ico_source_parent_class)
  269. ->build(object);
  270. }
  271. static void
  272. vips_foreign_load_ico_source_class_init(
  273. VipsForeignLoadIcoSourceClass *class)
  274. {
  275. GObjectClass *gobject_class = G_OBJECT_CLASS(class);
  276. VipsObjectClass *object_class = (VipsObjectClass *) class;
  277. VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class);
  278. VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
  279. gobject_class->set_property = vips_object_set_property;
  280. gobject_class->get_property = vips_object_get_property;
  281. object_class->nickname = "icoload_source";
  282. object_class->description = "load image from ico source";
  283. object_class->build = vips_foreign_load_ico_source_build;
  284. load_class->is_a_source = vips_foreign_load_ico_source_is_a_source;
  285. VIPS_ARG_OBJECT(class, "source", 1,
  286. "Source",
  287. "Source to load from",
  288. VIPS_ARGUMENT_REQUIRED_INPUT,
  289. G_STRUCT_OFFSET(VipsForeignLoadIcoSource, source),
  290. VIPS_TYPE_SOURCE);
  291. }
  292. static void
  293. vips_foreign_load_ico_source_init(VipsForeignLoadIcoSource *source)
  294. {
  295. }
  296. /**
  297. * vips_icoload_source:
  298. * @source: source to load
  299. * @out: (out): image to write
  300. * @...: `NULL`-terminated list of optional named arguments
  301. *
  302. * Read a RAWRGB-formatted memory block into a VIPS image.
  303. *
  304. * Returns: 0 on success, -1 on error.
  305. */
  306. int
  307. vips_icoload_source(VipsSource *source, VipsImage **out, ...)
  308. {
  309. va_list ap;
  310. int result;
  311. va_start(ap, out);
  312. result = vips_call_split("icoload_source", ap, source, out);
  313. va_end(ap);
  314. return result;
  315. }
  316. // wrapper function which hiders varargs (...) from CGo
  317. int
  318. vips_icoload_source_go(VipsImgproxySource *source, VipsImage **out)
  319. {
  320. return vips_icoload_source(VIPS_SOURCE(source), out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
  321. }