Skip to content

Commit 8247089

Browse files
committed
Port "Transparent support for writing 1bpp and 4bpp BMPs"
Cf. <libgd/libgd#913>
1 parent c1c6520 commit 8247089

File tree

2 files changed

+162
-19
lines changed

2 files changed

+162
-19
lines changed

ext/gd/libgd/gd_bmp.c

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
#include "gdhelpers.h"
2626
#include "bmp.h"
2727

28-
static int compress_row(unsigned char *uncompressed_row, int length);
29-
static int build_rle_packet(unsigned char *row, int packet_type, int length, unsigned char *data);
28+
static int compress_row(unsigned char *uncompressed_row, int length, int ppbyte, int nibble);
29+
static int build_rle_packet(unsigned char *row, int packet_type, int length, unsigned char *data, int ppbyte, int nibble);
3030

3131
static int bmp_read_header(gdIOCtxPtr infile, bmp_hdr_t *hdr);
3232
static int bmp_read_info(gdIOCtxPtr infile, bmp_info_t *info);
@@ -90,6 +90,7 @@ void gdImageBmp(gdImagePtr im, FILE *outFile, int compression)
9090
*/
9191
void gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression)
9292
{
93+
int bpp, ppbyte, compression_method;
9394
int bitmap_size = 0, info_size, total_size, padding;
9495
int i, row, xpos, pixel;
9596
int error = 0;
@@ -116,7 +117,17 @@ void gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression)
116117
}
117118
}
118119

119-
bitmap_size = ((im->sx * (im->trueColor ? 24 : 8)) / 8) * im->sy;
120+
if (im->trueColor) {
121+
bpp = 24;
122+
} else if (im->colorsTotal <= 2 && !compression) {
123+
bpp = 1;
124+
} else if (im->colorsTotal <= 16) {
125+
bpp = 4;
126+
} else {
127+
bpp = 8;
128+
}
129+
ppbyte = 8 / bpp; /* for palette images */
130+
bitmap_size = ((im->sx * bpp + 7) / 8) * im->sy;
120131

121132
/* 40 byte Windows v3 header */
122133
info_size = BMP_WINDOWS_V3;
@@ -132,6 +143,8 @@ void gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression)
132143
/* bitmap header + info header + data */
133144
total_size = 14 + info_size + bitmap_size;
134145

146+
compression_method = (compression ? (bpp >= 8 ? BMP_BI_RLE8 : BMP_BI_RLE4) : BMP_BI_RGB);
147+
135148
/* write bmp header info */
136149
gdPutBuf("BM", 2, out);
137150
gdBMPPutInt(out, total_size);
@@ -144,16 +157,20 @@ void gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression)
144157
gdBMPPutInt(out, im->sx); /* width */
145158
gdBMPPutInt(out, im->sy); /* height */
146159
gdBMPPutWord(out, 1); /* colour planes */
147-
gdBMPPutWord(out, (im->trueColor ? 24 : 8)); /* bit count */
148-
gdBMPPutInt(out, (compression ? BMP_BI_RLE8 : BMP_BI_RGB)); /* compression */
160+
gdBMPPutWord(out, bpp); /* bit count */
161+
gdBMPPutInt(out, compression_method); /* compression */
149162
gdBMPPutInt(out, bitmap_size); /* image size */
150163
gdBMPPutInt(out, 0); /* H resolution */
151164
gdBMPPutInt(out, 0); /* V ressolution */
152165
gdBMPPutInt(out, im->colorsTotal); /* colours used */
153166
gdBMPPutInt(out, 0); /* important colours */
154167

155168
/* The line must be divisible by 4, else it's padded with NULLs */
156-
padding = ((int)(im->trueColor ? 3 : 1) * im->sx) % 4;
169+
if (bpp < 8) {
170+
padding = (im->sx + ppbyte - 1) / ppbyte % 4;
171+
} else {
172+
padding = ((int)(im->trueColor ? 3 : 1) * im->sx) % 4;
173+
}
157174
if (padding) {
158175
padding = 4 - padding;
159176
}
@@ -177,13 +194,37 @@ void gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression)
177194
}
178195

179196
for (row = (im->sy - 1); row >= 0; row--) {
197+
int byte = 0;
180198
if (compression) {
181199
memset (uncompressed_row, 0, gdImageSX(im));
182200
}
183201

184202
for (xpos = 0; xpos < im->sx; xpos++) {
185-
if (compression) {
203+
if (compression && bpp < 8) {
204+
byte = (byte << bpp) + gdImageGetPixel(im, xpos, row);
205+
if (xpos % 2 == 1) {
206+
*uncompressed_row++ = (unsigned char) byte;
207+
byte = 0;
208+
} else if (xpos + 1 == im->sx) {
209+
/* imcomplete byte at end of row */
210+
byte <<= bpp;
211+
*uncompressed_row++ = (unsigned char) byte;
212+
byte = 0;
213+
}
214+
} else if (compression) {
186215
*uncompressed_row++ = (unsigned char)gdImageGetPixel(im, xpos, row);
216+
} else if (bpp < 8) {
217+
int next_chunk_in_byte = (xpos + 1) % ppbyte;
218+
byte = (byte << bpp) + gdImageGetPixel(im, xpos, row);
219+
if (next_chunk_in_byte == 0) {
220+
Putchar(byte, out);
221+
byte = 0;
222+
} else if (xpos + 1 == im->sx) {
223+
/* imcomplete byte at end of row */
224+
byte <<= bpp * (ppbyte - next_chunk_in_byte);
225+
Putchar(byte, out);
226+
byte = 0;
227+
}
187228
} else {
188229
Putchar(gdImageGetPixel(im, xpos, row), out);
189230
}
@@ -196,8 +237,10 @@ void gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression)
196237
}
197238
} else {
198239
int compressed_size = 0;
240+
int length = (gdImageSX(im) + ppbyte - 1) / ppbyte;
241+
int nibble = gdImageSX(im) % ppbyte;
199242
uncompressed_row = uncompressed_row_start;
200-
if ((compressed_size = compress_row(uncompressed_row, gdImageSX(im))) < 0) {
243+
if ((compressed_size = compress_row(uncompressed_row, length, ppbyte, nibble)) < 0) {
201244
error = 1;
202245
break;
203246
}
@@ -289,7 +332,7 @@ void gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression)
289332
return;
290333
}
291334

292-
static int compress_row(unsigned char *row, int length)
335+
static int compress_row(unsigned char *row, int length, int ppbyte, int nibble)
293336
{
294337
int rle_type = 0;
295338
int compressed_length = 0;
@@ -321,9 +364,9 @@ static int compress_row(unsigned char *row, int length)
321364
}
322365

323366
if (rle_type == BMP_RLE_TYPE_RLE) {
324-
if (compressed_run >= 128 || memcmp(uncompressed_rowp, uncompressed_rowp - 1, 1) != 0) {
367+
if (compressed_run >= 127 || memcmp(uncompressed_rowp, uncompressed_rowp - 1, 1) != 0) {
325368
/* more than what we can store in a single run or run is over due to non match, force write */
326-
rle_compression = build_rle_packet(row, rle_type, compressed_run, uncompressed_row);
369+
rle_compression = build_rle_packet(row, rle_type, compressed_run, uncompressed_row, ppbyte, 0);
327370
row += rle_compression;
328371
compressed_length += rle_compression;
329372
compressed_run = 0;
@@ -333,9 +376,9 @@ static int compress_row(unsigned char *row, int length)
333376
uncompressed_rowp++;
334377
}
335378
} else {
336-
if (compressed_run >= 128 || memcmp(uncompressed_rowp, uncompressed_rowp - 1, 1) == 0) {
379+
if (compressed_run >= 127 || memcmp(uncompressed_rowp, uncompressed_rowp - 1, 1) == 0) {
337380
/* more than what we can store in a single run or run is over due to match, force write */
338-
rle_compression = build_rle_packet(row, rle_type, compressed_run, uncompressed_row);
381+
rle_compression = build_rle_packet(row, rle_type, compressed_run, uncompressed_row, ppbyte, 0);
339382
row += rle_compression;
340383
compressed_length += rle_compression;
341384
compressed_run = 0;
@@ -349,19 +392,20 @@ static int compress_row(unsigned char *row, int length)
349392
}
350393
}
351394

395+
/* the following condition is always true */
352396
if (compressed_run) {
353-
compressed_length += build_rle_packet(row, rle_type, compressed_run, uncompressed_row);
397+
compressed_length += build_rle_packet(row, rle_type, compressed_run, uncompressed_row, ppbyte, nibble);
354398
}
355399

356400
gdFree(uncompressed_start);
357401

358402
return compressed_length;
359403
}
360404

361-
static int build_rle_packet(unsigned char *row, int packet_type, int length, unsigned char *data)
405+
static int build_rle_packet(unsigned char *row, int packet_type, int length, unsigned char *data, int ppbyte, int nibble)
362406
{
363407
int compressed_size = 0;
364-
if (length < 1 || length > 128) {
408+
if (length < 1 || length > 127) {
365409
return 0;
366410
}
367411

@@ -370,15 +414,15 @@ static int build_rle_packet(unsigned char *row, int packet_type, int length, uns
370414
int i = 0;
371415
for (i = 0; i < length; i++) {
372416
compressed_size += 2;
373-
memset(row, 1, 1);
417+
memset(row, ppbyte * 1 - (i + 1 == length ? nibble : 0), 1);
374418
row++;
375419

376420
memcpy(row, data++, 1);
377421
row++;
378422
}
379423
} else if (packet_type == BMP_RLE_TYPE_RLE) {
380424
compressed_size = 2;
381-
memset(row, length, 1);
425+
memset(row, ppbyte * length - nibble, 1);
382426
row++;
383427

384428
memcpy(row, data, 1);
@@ -388,7 +432,7 @@ static int build_rle_packet(unsigned char *row, int packet_type, int length, uns
388432
memset(row, BMP_RLE_COMMAND, 1);
389433
row++;
390434

391-
memset(row, length, 1);
435+
memset(row, ppbyte * length - nibble, 1);
392436
row++;
393437

394438
memcpy(row, data, length);

ext/gd/tests/bmp_low_depth.phpt

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
--TEST--
2+
Bitmaps with bpp < 8
3+
--EXTENSIONS--
4+
gd
5+
--FILE--
6+
<?php
7+
require __DIR__ . "/func.inc";
8+
9+
$filename = __DIR__ . "/bmp_low_depth.bmp";
10+
11+
// 2 color image
12+
$im1 = imagecreate(99, 99); // odd pixels per row!
13+
imagecolorallocate($im1, 0, 0, 0); // background
14+
$fg = imagecolorallocate($im1, 255, 255, 255);
15+
imagerectangle($im1, 0, 0, 98, 98, $fg); // border to verify edge case
16+
imageellipse($im1, 49, 49, 75, 75, $fg);
17+
18+
echo "1bpp uncompressed\n";
19+
imagebmp($im1, $filename, false);
20+
$data = file_get_contents($filename, false, null, 0x1c, 6);
21+
var_dump(unpack("vbpp/Vcomp", $data));
22+
$im2 = imagecreatefrombmp($filename);
23+
test_image_equals_image($im1, $im2);
24+
25+
echo "\n1bpp compressed\n";
26+
imagebmp($im1, $filename, true);
27+
$data = file_get_contents($filename, false, null, 0x1c, 6);
28+
var_dump(unpack("vbpp/Vcomp", $data));
29+
$im2 = imagecreatefrombmp($filename);
30+
test_image_equals_image($im1, $im2);
31+
32+
// 5 color image
33+
$r = imagecolorallocate($im1, 255, 0, 0);
34+
$g = imagecolorallocate($im1, 0, 255, 0);
35+
$b = imagecolorallocate($im1, 0, 0, 255);
36+
imageellipse($im1, 17, 17, 28, 28, $r);
37+
imageellipse($im1, 49, 17, 28, 28, $r);
38+
imageellipse($im1, 81, 17, 28, 28, $r);
39+
imageellipse($im1, 17, 49, 28, 28, $g);
40+
imageellipse($im1, 49, 49, 28, 28, $g);
41+
imageellipse($im1, 81, 49, 28, 28, $g);
42+
imageellipse($im1, 17, 81, 28, 28, $b);
43+
imageellipse($im1, 49, 81, 28, 28, $b);
44+
imageellipse($im1, 81, 81, 28, 28, $b);
45+
46+
echo "\n4bpp uncompressed\n";
47+
imagebmp($im1, $filename, false);
48+
$data = file_get_contents($filename, false, null, 0x1c, 6);
49+
var_dump(unpack("vbpp/Vcomp", $data));
50+
$im2 = imagecreatefrombmp($filename);
51+
test_image_equals_image($im1, $im2);
52+
53+
echo "\n4bpp compressed\n";
54+
imagebmp($im1, $filename, true);
55+
$data = file_get_contents($filename, false, null, 0x1c, 6);
56+
var_dump(unpack("vbpp/Vcomp", $data));
57+
$im2 = imagecreatefrombmp($filename);
58+
test_image_equals_image($im1, $im2);
59+
?>
60+
--EXPECT--
61+
1bpp uncompressed
62+
array(2) {
63+
["bpp"]=>
64+
int(1)
65+
["comp"]=>
66+
int(0)
67+
}
68+
The images are equal.
69+
70+
1bpp compressed
71+
array(2) {
72+
["bpp"]=>
73+
int(4)
74+
["comp"]=>
75+
int(2)
76+
}
77+
The images are equal.
78+
79+
4bpp uncompressed
80+
array(2) {
81+
["bpp"]=>
82+
int(4)
83+
["comp"]=>
84+
int(0)
85+
}
86+
The images are equal.
87+
88+
4bpp compressed
89+
array(2) {
90+
["bpp"]=>
91+
int(4)
92+
["comp"]=>
93+
int(2)
94+
}
95+
The images are equal.
96+
--CLEAN--
97+
<?php
98+
@unlink(__DIR__ . "/bmp_low_depth.bmp");
99+
?>

0 commit comments

Comments
 (0)