icoload.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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_ico_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. typedef struct _VipsForeignLoadIcoBuffer {
  297. VipsForeignLoadIco parent_object;
  298. /* Load from a buffer.
  299. */
  300. VipsBlob *blob;
  301. } VipsForeignLoadIcoBuffer;
  302. typedef VipsForeignLoadIcoClass VipsForeignLoadIcoBufferClass;
  303. G_DEFINE_TYPE(VipsForeignLoadIcoBuffer, vips_foreign_load_ico_buffer,
  304. vips_foreign_load_ico_get_type());
  305. static int
  306. vips_foreign_load_ico_buffer_build(VipsObject *object)
  307. {
  308. VipsForeignLoadIco *ico = (VipsForeignLoadIco *) object;
  309. VipsForeignLoadIcoBuffer *buffer = (VipsForeignLoadIcoBuffer *) object;
  310. if (buffer->blob &&
  311. !(ico->source = vips_source_new_from_memory(
  312. VIPS_AREA(buffer->blob)->data,
  313. VIPS_AREA(buffer->blob)->length)))
  314. return -1;
  315. return VIPS_OBJECT_CLASS(vips_foreign_load_ico_buffer_parent_class)
  316. ->build(object);
  317. }
  318. /**
  319. * Checks if the source is a ICO image
  320. */
  321. static gboolean
  322. vips_foreign_load_ico_buffer_is_a_buffer(const void *buf, size_t len)
  323. {
  324. if (len < 4) {
  325. vips_error("vips_foreign_load_ico_buffer_is_a_buffer", "buffer too short");
  326. return 0;
  327. }
  328. return ((VipsPel *) buf)[0] == 0 && ((VipsPel *) buf)[1] == 0 && ((VipsPel *) buf)[2] == 1 && ((VipsPel *) buf)[3] == 0;
  329. }
  330. static void
  331. vips_foreign_load_ico_buffer_class_init(VipsForeignLoadIcoBufferClass *class)
  332. {
  333. GObjectClass *gobject_class = G_OBJECT_CLASS(class);
  334. VipsObjectClass *object_class = (VipsObjectClass *) class;
  335. VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
  336. gobject_class->set_property = vips_object_set_property;
  337. gobject_class->get_property = vips_object_get_property;
  338. object_class->nickname = "icoload_buffer";
  339. object_class->description = "load ico from buffer";
  340. object_class->build = vips_foreign_load_ico_buffer_build;
  341. load_class->is_a_buffer = vips_foreign_load_ico_buffer_is_a_buffer;
  342. VIPS_ARG_BOXED(class, "buffer", 1,
  343. "Buffer",
  344. "Buffer to load from",
  345. VIPS_ARGUMENT_REQUIRED_INPUT,
  346. G_STRUCT_OFFSET(VipsForeignLoadIcoBuffer, blob),
  347. VIPS_TYPE_BLOB);
  348. }
  349. static void
  350. vips_foreign_load_ico_buffer_init(VipsForeignLoadIcoBuffer *buffer)
  351. {
  352. }
  353. /**
  354. * vips_icoload_buffer:
  355. * @buf: (array length=len) (element-type guint8): memory area to load
  356. * @len: (type gsize): size of memory area
  357. * @out: (out): image to write
  358. * @...: `NULL`-terminated list of optional named arguments
  359. *
  360. * Exactly as [ctor@Image.pngload], but read from a PNG-formatted memory block.
  361. *
  362. * You must not free the buffer while @out is active. The
  363. * [signal@Object::postclose] signal on @out is a good place to free.
  364. *
  365. * ::: seealso
  366. * [ctor@Image.pngload].
  367. *
  368. * Returns: 0 on success, -1 on error.
  369. */
  370. int
  371. vips_icoload_buffer(void *buf, size_t len, VipsImage **out, ...)
  372. {
  373. va_list ap;
  374. VipsBlob *blob;
  375. int result;
  376. /* We don't take a copy of the data or free it.
  377. */
  378. blob = vips_blob_new(NULL, buf, len);
  379. va_start(ap, out);
  380. result = vips_call_split("icoload_buffer", ap, blob, out);
  381. va_end(ap);
  382. vips_area_unref(VIPS_AREA(blob));
  383. return result;
  384. }
  385. /**
  386. * vips_icoload_source:
  387. * @source: source to load
  388. * @out: (out): image to write
  389. * @...: `NULL`-terminated list of optional named arguments
  390. *
  391. * Read a RAWRGB-formatted memory block into a VIPS image.
  392. *
  393. * Returns: 0 on success, -1 on error.
  394. */
  395. int
  396. vips_icoload_source(VipsSource *source, VipsImage **out, ...)
  397. {
  398. va_list ap;
  399. int result;
  400. va_start(ap, out);
  401. result = vips_call_split("icoload_source", ap, source, out);
  402. va_end(ap);
  403. return result;
  404. }
  405. // wrapper function which hiders varargs (...) from CGo
  406. int
  407. vips_icoload_source_go(VipsImgproxySource *source, VipsImage **out, ImgproxyLoadOptions lo)
  408. {
  409. return vips_icoload_source(VIPS_SOURCE(source), out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
  410. }