bmpsave.c 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. // BMP saver
  2. #include "vips.h"
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <stdint.h>
  6. /* Save a bit of typing.
  7. */
  8. #define UC VIPS_FORMAT_UCHAR
  9. static VipsBandFormat bandfmt_bmp[10] = {
  10. /* Band format: UC C US S UI I F X D DX */
  11. /* Promotion: */ UC, UC, UC, UC, UC, UC, UC, UC, UC, UC
  12. };
  13. // BMP BITMAPINFOHEADERV5 file header struct, ((packed)) since we
  14. // do not want any compiler-induced padding
  15. typedef struct __attribute__((packed)) _BmpHeader {
  16. uint8_t sig[2]; // Signature 'BM'
  17. uint32_t file_size; // File size in bytes
  18. uint16_t reserved[2]; // Reserved fields
  19. uint32_t pix_offset; // Offset to pixel data
  20. uint32_t dib_header_size; // DIB header size
  21. int32_t width; // Image width
  22. int32_t height; // Image height
  23. uint16_t color_plane; // Number of color planes
  24. uint16_t bpp; // Bits per pixel
  25. uint32_t compression; // Compression method
  26. uint32_t image_size; // Image size
  27. uint32_t x_pixels_per_meter; // Horizontal resolution
  28. uint32_t y_pixels_per_meter; // Vertical resolution
  29. uint32_t color_use; // Number of colors in palette
  30. uint32_t color_important; // Number of important colors
  31. uint32_t rmask; // Red mask
  32. uint32_t gmask; // Green mask
  33. uint32_t bmask; // Blue mask
  34. uint32_t amask; // Alpha mask (optional, only for 32 bpp BMP files)
  35. uint8_t cs_type[4]; // Color space type (B G R s)
  36. uint8_t cs[36]; // CIEXYZTRIPLE Color Space
  37. uint32_t red_gamma; // Red gamma
  38. uint32_t green_gamma; // Green gamma
  39. uint32_t blue_gamma; // Blue gamma
  40. uint32_t intent;
  41. uint32_t profile_data; // Profile data (optional, only for 32 bpp BMP files)
  42. uint32_t profile_size;
  43. uint32_t reserved_5;
  44. } BmpHeader;
  45. typedef struct _VipsForeignSaveBmp {
  46. VipsForeignSave parent_object;
  47. VipsTarget *target;
  48. VipsPel *line_buffer;
  49. uint16_t bands;
  50. uint32_t line_size;
  51. } VipsForeignSaveBmp;
  52. typedef VipsForeignSaveClass VipsForeignSaveBmpClass;
  53. G_DEFINE_ABSTRACT_TYPE(VipsForeignSaveBmp, vips_foreign_save_bmp,
  54. VIPS_TYPE_FOREIGN_SAVE);
  55. static void
  56. vips_foreign_save_bmp_dispose(GObject *gobject)
  57. {
  58. VipsForeignSaveBmp *bmp = (VipsForeignSaveBmp *) gobject;
  59. VIPS_UNREF(bmp->target);
  60. G_OBJECT_CLASS(vips_foreign_save_bmp_parent_class)->dispose(gobject);
  61. }
  62. static int
  63. vips_foreign_save_bmp_block(VipsRegion *region, VipsRect *area, void *a)
  64. {
  65. VipsForeignSaveBmp *bmp = (VipsForeignSaveBmp *) a;
  66. VipsImage *image = region->im;
  67. // This is the position in the source image
  68. uint32_t source_row_size = region->im->Xsize * bmp->bands;
  69. for (int y = 0; y < area->height; y++) {
  70. VipsPel *src = VIPS_REGION_ADDR(region, 0, area->top + y);
  71. VipsPel *dst = bmp->line_buffer;
  72. for (int x = 0; x < source_row_size; x += bmp->bands) {
  73. dst[0] = src[2]; // B
  74. dst[1] = src[1]; // G
  75. dst[2] = src[0]; // R
  76. if (bmp->bands == 4) {
  77. dst[3] = src[3]; // A
  78. }
  79. dst += bmp->bands;
  80. src += bmp->bands;
  81. }
  82. if (vips_target_write(bmp->target, bmp->line_buffer, bmp->line_size) < 0) {
  83. vips_error("vips_foreign_save_bmp_build", "unable to write BMP pixel data to target");
  84. return -1;
  85. }
  86. }
  87. return 0;
  88. }
  89. static int
  90. vips_foreign_save_bmp_build(VipsObject *object)
  91. {
  92. VipsForeignSave *save = (VipsForeignSave *) object;
  93. VipsForeignSaveBmp *bmp = (VipsForeignSaveBmp *) object;
  94. VipsImage *in;
  95. if (VIPS_OBJECT_CLASS(vips_foreign_save_bmp_parent_class)->build(object))
  96. return -1;
  97. in = save->ready; // shortcut
  98. // bands (3 or 4) * 8 bits
  99. int bands = vips_image_get_bands(in);
  100. if ((bands < 3) || (bands > 4)) {
  101. vips_error("vips_foreign_save_bmp_build", "BMP source file must have 3 or 4 bands (RGB or RGBA)");
  102. return -1;
  103. }
  104. int bpp = bands * 8;
  105. // Target image line size trimmed to 4 bytes.
  106. uint32_t line_size = (in->Xsize * bands + 3) & (~3);
  107. uint32_t image_size = in->Ysize * line_size;
  108. // pix_offset = header size + file size
  109. uint32_t pix_offset = BMP_FILE_HEADER_LEN + BMP_V5_INFO_HEADER_LEN;
  110. // Format BMP file header. We write 24/32 bpp BMP files only with no compression.
  111. BmpHeader header;
  112. header.sig[0] = 'B';
  113. header.sig[1] = 'M';
  114. header.file_size = GUINT32_TO_LE(pix_offset + image_size);
  115. header.reserved[0] = 0;
  116. header.reserved[1] = 0;
  117. header.pix_offset = GUINT32_TO_LE(pix_offset);
  118. header.dib_header_size = GUINT32_TO_LE(BMP_V5_INFO_HEADER_LEN);
  119. header.width = GINT32_TO_LE(in->Xsize);
  120. header.height = GINT32_TO_LE(-in->Ysize);
  121. header.color_plane = GUINT16_TO_LE(1);
  122. header.bpp = GUINT16_TO_LE(bpp);
  123. header.compression = COMPRESSION_BI_RGB;
  124. header.image_size = GUINT32_TO_LE(image_size);
  125. header.x_pixels_per_meter = 0; // GUINT32_TO_LE(2835);
  126. header.y_pixels_per_meter = 0; // GUINT32_TO_LE(2835);
  127. header.color_use = 0;
  128. header.color_important = 0;
  129. header.rmask = GUINT32_TO_LE(0x00FF0000); // Standard says that masks are in BE order
  130. header.gmask = GUINT32_TO_LE(0x0000FF00);
  131. header.bmask = GUINT32_TO_LE(0x000000FF);
  132. header.amask = GUINT32_TO_LE(0xFF000000);
  133. header.cs_type[0] = 'B'; // Image color profile
  134. header.cs_type[1] = 'G';
  135. header.cs_type[2] = 'R';
  136. header.cs_type[3] = 's';
  137. memset(header.cs, 0, sizeof(header.cs)); // CIEXYZTRIPLE Color Space
  138. header.red_gamma = 0;
  139. header.green_gamma = 0;
  140. header.blue_gamma = 0;
  141. header.intent = GUINT32_TO_LE(4); // IMAGES intent, must be 4
  142. header.profile_data = 0;
  143. header.profile_size = 0;
  144. header.reserved_5 = 0;
  145. if (vips_target_write(bmp->target, &header, sizeof(header)) < 0) {
  146. vips_error("vips_foreign_save_bmp_build", "unable to write BMP header to target");
  147. return -1;
  148. }
  149. // Allocate a line buffer for the target image
  150. bmp->line_buffer = VIPS_MALLOC(save, line_size);
  151. bmp->bands = bands;
  152. bmp->line_size = line_size;
  153. // save image async
  154. if (vips_sink_disc(in, vips_foreign_save_bmp_block, bmp))
  155. return -1;
  156. if (vips_target_end(bmp->target))
  157. return -1;
  158. return 0;
  159. }
  160. static void
  161. vips_foreign_save_bmp_class_init(VipsForeignSaveBmpClass *class)
  162. {
  163. GObjectClass *gobject_class = G_OBJECT_CLASS(class);
  164. VipsObjectClass *object_class = (VipsObjectClass *) class;
  165. VipsForeignClass *foreign_class = (VipsForeignClass *) class;
  166. VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class;
  167. gobject_class->dispose = vips_foreign_save_bmp_dispose;
  168. gobject_class->set_property = vips_object_set_property;
  169. gobject_class->get_property = vips_object_get_property;
  170. object_class->nickname = "bmpsave_base";
  171. object_class->description = "save bmp";
  172. object_class->build = vips_foreign_save_bmp_build;
  173. // We do not support saving monochrome images yet (VIPS_FOREIGN_SAVEABLE_MONO)
  174. // In v4 we will support it, so we leave it here commented out
  175. save_class->saveable =
  176. VIPS_SAVEABLE_RGB | // latest vips: VIPS_FOREIGN_SAVEABLE_RGB
  177. VIPS_SAVEABLE_RGBA; // latest vips: VIPS_FOREIGN_SAVEABLE_ALPHA
  178. save_class->format_table = bandfmt_bmp;
  179. }
  180. static void
  181. vips_foreign_save_bmp_init(VipsForeignSaveBmp *bmp)
  182. {
  183. }
  184. typedef struct _VipsForeignSaveBmpTarget {
  185. VipsForeignSaveBmp parent_object;
  186. VipsTarget *target;
  187. } VipsForeignSaveBmpTarget;
  188. typedef VipsForeignSaveBmpClass VipsForeignSaveBmpTargetClass;
  189. G_DEFINE_TYPE(VipsForeignSaveBmpTarget, vips_foreign_save_bmp_target,
  190. vips_foreign_save_bmp_get_type());
  191. static int
  192. vips_foreign_save_bmp_target_build(VipsObject *object)
  193. {
  194. VipsForeignSaveBmp *bmp = (VipsForeignSaveBmp *) object;
  195. VipsForeignSaveBmpTarget *target = (VipsForeignSaveBmpTarget *) object;
  196. bmp->target = target->target;
  197. g_object_ref(bmp->target);
  198. return VIPS_OBJECT_CLASS(vips_foreign_save_bmp_target_parent_class)
  199. ->build(object);
  200. }
  201. static void
  202. vips_foreign_save_bmp_target_class_init(VipsForeignSaveBmpTargetClass *class)
  203. {
  204. GObjectClass *gobject_class = G_OBJECT_CLASS(class);
  205. VipsObjectClass *object_class = (VipsObjectClass *) class;
  206. gobject_class->set_property = vips_object_set_property;
  207. gobject_class->get_property = vips_object_get_property;
  208. object_class->nickname = "bmpsave_target";
  209. object_class->description = "save image to target as PNG";
  210. object_class->build = vips_foreign_save_bmp_target_build;
  211. VIPS_ARG_OBJECT(class, "target", 1,
  212. "Target",
  213. "Target to save to",
  214. VIPS_ARGUMENT_REQUIRED_INPUT,
  215. G_STRUCT_OFFSET(VipsForeignSaveBmpTarget, target),
  216. VIPS_TYPE_TARGET);
  217. }
  218. static void
  219. vips_foreign_save_bmp_target_init(VipsForeignSaveBmpTarget *target)
  220. {
  221. }
  222. /**
  223. * vips_bmpsave_target: (method)
  224. * @in: image to save
  225. * @target: save image to this target
  226. * @...: `NULL`-terminated list of optional named arguments
  227. *
  228. * As [method@Image.bmpsave], but save to a target.
  229. *
  230. * ::: seealso
  231. * [method@Image.bmpsave], [method@Image.write_to_target].
  232. *
  233. * Returns: 0 on success, -1 on error.
  234. */
  235. int
  236. vips_bmpsave_target(VipsImage *in, VipsTarget *target, ...)
  237. {
  238. va_list ap;
  239. int result;
  240. va_start(ap, target);
  241. result = vips_call_split("bmpsave_target", ap, in, target);
  242. va_end(ap);
  243. return result;
  244. }
  245. // wrapper function which hides varargs (...) from CGo
  246. int
  247. vips_bmpsave_target_go(VipsImage *in, VipsTarget *target, ImgproxySaveOptions opts)
  248. {
  249. return vips_bmpsave_target(in, VIPS_TARGET(target), NULL);
  250. }