Skip to content

Commit 8c55eec

Browse files
committed
Add hasAlpha property to output info #4500
1 parent bd0c2c7 commit 8c55eec

7 files changed

Lines changed: 70 additions & 24 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ jobs:
312312
needs: lint
313313
name: "build-wasm32 [package]"
314314
runs-on: ubuntu-24.04
315-
container: "emscripten/emsdk:6.0.0"
315+
container: "emscripten/emsdk:6.0.1"
316316
steps:
317317
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
318318
with:

docs/src/content/docs/changelog/v0.35.3.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ slug: changelog/v0.35.3
55

66
* Tighten verification of `text` dimensions, TIFF tile dimensions and `extend` values.
77

8+
* Improve code bundler support by resolving path to libvips binary.
9+
10+
* Add `hasAlpha` property to output `info`.
11+
[#4500](https://github.com/lovell/sharp/issues/4500)
12+
813
* Bound `clahe` width and height to avoid signed overflow.
914
[#4551]((https://github.com/lovell/sharp/pull/4551)
1015
[@metsw24-max](https://github.com/metsw24-max)
@@ -32,3 +37,7 @@ slug: changelog/v0.35.3
3237
* Tighten recomb and affine matrix verification.
3338
[#4560](https://github.com/lovell/sharp/pull/4560)
3439
[@chatman-media](https://github.com/chatman-media)
40+
41+
* Verify cache memory limit to avoid overflow.
42+
[#4561](https://github.com/lovell/sharp/pull/4561)
43+
[@metsw24-max](https://github.com/metsw24-max)

lib/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,6 +1783,8 @@ declare namespace sharp {
17831783
channels: Channels;
17841784
/** indicating if premultiplication was used */
17851785
premultiplied: boolean;
1786+
/** Indicates if the output image has an alpha channel */
1787+
hasAlpha: boolean;
17861788
/** Only defined when using a crop strategy */
17871789
cropOffsetLeft?: number | undefined;
17881790
/** Only defined when using a crop strategy */

src/pipeline.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,7 @@ class PipelineWorker : public Napi::AsyncWorker {
944944
baton->pageHeightOut = image.get_int(VIPS_META_PAGE_HEIGHT);
945945
baton->pagesOut = image.get_int(VIPS_META_N_PAGES);
946946
}
947+
baton->hasAlphaOut = image.has_alpha();
947948

948949
// Output
949950
sharp::SetTimeout(image, baton->timeoutSeconds);
@@ -983,6 +984,7 @@ class PipelineWorker : public Napi::AsyncWorker {
983984
} else {
984985
baton->channels = std::min(baton->channels, 3);
985986
}
987+
baton->hasAlphaOut = false;
986988
} else if (baton->formatOut == "jp2" || (baton->formatOut == "input"
987989
&& inputImageType == sharp::ImageType::JP2)) {
988990
// Write JP2 to Buffer
@@ -1065,6 +1067,7 @@ class PipelineWorker : public Napi::AsyncWorker {
10651067
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
10661068
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
10671069
baton->channels = std::min(baton->channels, 3);
1070+
baton->hasAlphaOut = false;
10681071
}
10691072
// Cast pixel values to float, if required
10701073
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
@@ -1124,6 +1127,9 @@ class PipelineWorker : public Napi::AsyncWorker {
11241127
area->free_fn = nullptr;
11251128
vips_area_unref(area);
11261129
baton->formatOut = "dz";
1130+
if (baton->tileFormat == "jpeg") {
1131+
baton->hasAlphaOut = false;
1132+
}
11271133
} else if (baton->formatOut == "jxl" ||
11281134
(baton->formatOut == "input" && inputImageType == sharp::ImageType::JXL)) {
11291135
// Write JXL to buffer
@@ -1211,6 +1217,7 @@ class PipelineWorker : public Napi::AsyncWorker {
12111217
}
12121218
baton->formatOut = "jpeg";
12131219
baton->channels = std::min(baton->channels, 3);
1220+
baton->hasAlphaOut = false;
12141221
} else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) ||
12151222
(willMatchInput && (inputImageType == sharp::ImageType::JP2))) {
12161223
// Write JP2 to file
@@ -1277,6 +1284,7 @@ class PipelineWorker : public Napi::AsyncWorker {
12771284
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
12781285
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
12791286
baton->channels = std::min(baton->channels, 3);
1287+
baton->hasAlphaOut = false;
12801288
}
12811289
// Cast pixel values to float, if required
12821290
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
@@ -1337,6 +1345,9 @@ class PipelineWorker : public Napi::AsyncWorker {
13371345
vips::VOption *options = BuildOptionsDZ(baton);
13381346
image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
13391347
baton->formatOut = "dz";
1348+
if (baton->tileFormat == "jpeg") {
1349+
baton->hasAlphaOut = false;
1350+
}
13401351
} else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
13411352
(willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
13421353
// Write V to file
@@ -1421,6 +1432,7 @@ class PipelineWorker : public Napi::AsyncWorker {
14211432
info.Set("pageHeight", static_cast<int32_t>(baton->pageHeightOut));
14221433
info.Set("pages", static_cast<int32_t>(baton->pagesOut));
14231434
}
1435+
info.Set("hasAlpha", baton->hasAlphaOut);
14241436

14251437
if (baton->bufferOutLength > 0) {
14261438
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));

src/pipeline.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ struct PipelineBaton {
4949
int pageHeightOut;
5050
int pagesOut;
5151
bool typedArrayOut;
52+
bool hasAlphaOut;
5253
std::vector<Composite *> composite;
5354
std::vector<sharp::InputDescriptor *> joinChannelIn;
5455
int topOffsetPre;
@@ -249,6 +250,7 @@ struct PipelineBaton {
249250
pageHeightOut(0),
250251
pagesOut(0),
251252
typedArrayOut(false),
253+
hasAlphaOut(false),
252254
topOffsetPre(-1),
253255
topOffsetPost(-1),
254256
channels(0),

test/unit/io.js

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ suite('Input/output', () => {
2525
});
2626

2727
test('Read from File and write to Stream', async (t) => {
28-
t.plan(5);
28+
t.plan(6);
2929
const writable = createWriteStream(outputJpg);
3030
const closed = new Promise((resolve, reject) => {
3131
writable.once('close', resolve);
@@ -39,11 +39,12 @@ suite('Input/output', () => {
3939
t.assert.strictEqual('jpeg', info.format);
4040
t.assert.strictEqual(320, info.width);
4141
t.assert.strictEqual(240, info.height);
42+
t.assert.strictEqual(false, info.hasAlpha);
4243
await fs.rm(outputJpg);
4344
});
4445

4546
test('Read from Buffer and write to Stream', async (t) => {
46-
t.plan(5);
47+
t.plan(6);
4748
const inputJpgBuffer = await fs.readFile(fixtures.inputJpg);
4849
const writable = createWriteStream(outputJpg);
4950
const closed = new Promise((resolve, reject) => {
@@ -58,26 +59,28 @@ suite('Input/output', () => {
5859
t.assert.strictEqual('jpeg', info.format);
5960
t.assert.strictEqual(320, info.width);
6061
t.assert.strictEqual(240, info.height);
62+
t.assert.strictEqual(false, info.hasAlpha);
6163
await fs.rm(outputJpg);
6264
});
6365

6466
test('Read from Stream and write to File via callback', (t, done) => {
65-
t.plan(4);
67+
t.plan(5);
6668
const readable = createReadStream(fixtures.inputJpg);
6769
const pipeline = sharp().resize(320, 240).toFile(outputJpg, async (err, info) => {
6870
if (err) throw err;
6971
t.assert.strictEqual(true, info.size > 0);
7072
t.assert.strictEqual('jpeg', info.format);
7173
t.assert.strictEqual(320, info.width);
7274
t.assert.strictEqual(240, info.height);
75+
t.assert.strictEqual(false, info.hasAlpha);
7376
await fs.rm(outputJpg);
7477
done();
7578
});
7679
readable.pipe(pipeline);
7780
});
7881

7982
test('Read from Stream and write to Buffer via callback', (t, done) => {
80-
t.plan(5);
83+
t.plan(6);
8184
const readable = createReadStream(fixtures.inputJpg);
8285
const pipeline = sharp().resize(320, 240).toBuffer((err, data, info) => {
8386
if (err) throw err;
@@ -86,20 +89,22 @@ suite('Input/output', () => {
8689
t.assert.strictEqual('jpeg', info.format);
8790
t.assert.strictEqual(320, info.width);
8891
t.assert.strictEqual(240, info.height);
92+
t.assert.strictEqual(false, info.hasAlpha);
8993
done();
9094
});
9195
readable.pipe(pipeline);
9296
});
9397

9498
test('Read from File and write to Buffer via callback', (t, done) => {
95-
t.plan(6);
99+
t.plan(7);
96100
sharp(fixtures.inputJpg).resize(320, 240).toBuffer((err, data, info) => {
97101
t.assert.strictEqual(err, null);
98102
t.assert.strictEqual(true, data.length > 0);
99103
t.assert.strictEqual(data.length, info.size);
100104
t.assert.strictEqual('jpeg', info.format);
101105
t.assert.strictEqual(320, info.width);
102106
t.assert.strictEqual(240, info.height);
107+
t.assert.strictEqual(false, info.hasAlpha);
103108
done();
104109
});
105110
});
@@ -126,7 +131,7 @@ suite('Input/output', () => {
126131
});
127132

128133
test('Read from Stream and write to Buffer via Promise resolved with Object', async (t) => {
129-
t.plan(8);
134+
t.plan(9);
130135
const pipeline = sharp().resize(1, 1);
131136
createReadStream(fixtures.inputJpg).pipe(pipeline);
132137
const object = await pipeline.toBuffer({ resolveWithObject: true });
@@ -136,6 +141,7 @@ suite('Input/output', () => {
136141
t.assert.strictEqual(1, object.info.width);
137142
t.assert.strictEqual(1, object.info.height);
138143
t.assert.strictEqual(3, object.info.channels);
144+
t.assert.strictEqual(false, object.info.hasAlpha);
139145
t.assert.strictEqual(true, object.data instanceof Buffer);
140146
t.assert.strictEqual(true, object.data.length > 0);
141147
});
@@ -150,7 +156,7 @@ suite('Input/output', () => {
150156
});
151157

152158
test('Read from File and write to Buffer via Promise resolved with Object', async (t) => {
153-
t.plan(8);
159+
t.plan(9);
154160
const object = await sharp(fixtures.inputJpg)
155161
.resize(1, 1)
156162
.toBuffer({ resolveWithObject: true });
@@ -160,12 +166,13 @@ suite('Input/output', () => {
160166
t.assert.strictEqual(1, object.info.width);
161167
t.assert.strictEqual(1, object.info.height);
162168
t.assert.strictEqual(3, object.info.channels);
169+
t.assert.strictEqual(false, object.info.hasAlpha);
163170
t.assert.strictEqual(true, object.data instanceof Buffer);
164171
t.assert.strictEqual(true, object.data.length > 0);
165172
});
166173

167174
test('Read from Stream and write to Stream', async (t) => {
168-
t.plan(5);
175+
t.plan(6);
169176
const readable = createReadStream(fixtures.inputJpg);
170177
const writable = createWriteStream(outputJpg);
171178
const closed = new Promise((resolve, reject) => {
@@ -181,6 +188,7 @@ suite('Input/output', () => {
181188
t.assert.strictEqual('jpeg', info.format);
182189
t.assert.strictEqual(320, info.width);
183190
t.assert.strictEqual(240, info.height);
191+
t.assert.strictEqual(false, info.hasAlpha);
184192
await fs.rm(outputJpg);
185193
});
186194

@@ -256,7 +264,7 @@ suite('Input/output', () => {
256264
});
257265

258266
test('Stream should emit info event', async (t) => {
259-
t.plan(4);
267+
t.plan(5);
260268
const readable = createReadStream(fixtures.inputJpg);
261269
const writable = createWriteStream(outputJpg);
262270
const pipeline = sharp().resize(320, 240);
@@ -266,6 +274,7 @@ suite('Input/output', () => {
266274
t.assert.strictEqual(320, info.width);
267275
t.assert.strictEqual(240, info.height);
268276
t.assert.strictEqual(3, info.channels);
277+
t.assert.strictEqual(false, info.hasAlpha);
269278
resolve();
270279
});
271280
});
@@ -593,43 +602,46 @@ suite('Input/output', () => {
593602
});
594603

595604
test('Match PNG input', async (t) => {
596-
t.plan(4);
597-
const info = await sharp(fixtures.inputPng)
605+
t.plan(5);
606+
const info = await sharp(fixtures.inputPngRGBWithAlpha)
598607
.resize(320, 80)
599608
.toFile(outputZoinks);
600609
t.assert.strictEqual(true, info.size > 0);
601610
t.assert.strictEqual('png', info.format);
602611
t.assert.strictEqual(320, info.width);
603612
t.assert.strictEqual(80, info.height);
613+
t.assert.strictEqual(true, info.hasAlpha);
604614
await fs.rm(outputZoinks);
605615
});
606616

607617
test('Match WebP input', async (t) => {
608-
t.plan(4);
609-
const info = await sharp(fixtures.inputWebP)
618+
t.plan(5);
619+
const info = await sharp(fixtures.inputWebPWithTransparency)
610620
.resize(320, 80)
611621
.toFile(outputZoinks);
612622
t.assert.strictEqual(true, info.size > 0);
613623
t.assert.strictEqual('webp', info.format);
614624
t.assert.strictEqual(320, info.width);
615625
t.assert.strictEqual(80, info.height);
626+
t.assert.strictEqual(true, info.hasAlpha);
616627
await fs.rm(outputZoinks);
617628
});
618629

619630
test('Match TIFF input', async (t) => {
620-
t.plan(4);
631+
t.plan(5);
621632
const info = await sharp(fixtures.inputTiff)
622633
.resize(320, 80)
623634
.toFile(outputZoinks);
624635
t.assert.strictEqual(true, info.size > 0);
625636
t.assert.strictEqual('tiff', info.format);
626637
t.assert.strictEqual(320, info.width);
627638
t.assert.strictEqual(80, info.height);
639+
t.assert.strictEqual(false, info.hasAlpha);
628640
await fs.rm(outputZoinks);
629641
});
630642

631643
test('Force JPEG format for PNG input', async (t) => {
632-
t.plan(4);
644+
t.plan(5);
633645
const info = await sharp(fixtures.inputPng)
634646
.resize(320, 80)
635647
.jpeg()
@@ -638,19 +650,21 @@ suite('Input/output', () => {
638650
t.assert.strictEqual('jpeg', info.format);
639651
t.assert.strictEqual(320, info.width);
640652
t.assert.strictEqual(80, info.height);
653+
t.assert.strictEqual(false, info.hasAlpha);
641654
await fs.rm(outputZoinks);
642655
});
643656
});
644657

645658
test('Input and output formats match when not forcing', async (t) => {
646-
t.plan(3);
659+
t.plan(4);
647660
const { info } = await sharp(fixtures.inputJpg)
648661
.resize(320, 240)
649662
.png({ compressionLevel: 1, force: false })
650663
.toBuffer({ resolveWithObject: true });
651664
t.assert.strictEqual('jpeg', info.format);
652665
t.assert.strictEqual(320, info.width);
653666
t.assert.strictEqual(240, info.height);
667+
t.assert.strictEqual(false, info.hasAlpha);
654668
});
655669

656670
test('Can force output format with output chaining', async (t) => {
@@ -686,14 +700,15 @@ suite('Input/output', () => {
686700
});
687701

688702
test('Load Vips V file', async (t) => {
689-
t.plan(4);
703+
t.plan(5);
690704
const { data, info } = await sharp(fixtures.inputV)
691705
.jpeg()
692706
.toBuffer({ resolveWithObject: true });
693707
t.assert.strictEqual(true, data.length > 0);
694708
t.assert.strictEqual('jpeg', info.format);
695709
t.assert.strictEqual(70, info.width);
696710
t.assert.strictEqual(60, info.height);
711+
t.assert.strictEqual(false, info.hasAlpha);
697712
await fixtures.assertSimilar(fixtures.expected('vfile.jpg'), data);
698713
});
699714

@@ -1201,7 +1216,7 @@ suite('Input/output', () => {
12011216
});
12021217

12031218
test('Info event data', async (t) => {
1204-
t.plan(4);
1219+
t.plan(5);
12051220
const readable = createReadStream(fixtures.inputJPGBig);
12061221
const inPipeline = sharp()
12071222
.resize(840, 472)
@@ -1210,6 +1225,7 @@ suite('Input/output', () => {
12101225
t.assert.strictEqual(840, info.width);
12111226
t.assert.strictEqual(472, info.height);
12121227
t.assert.strictEqual(3, info.channels);
1228+
t.assert.strictEqual(false, info.hasAlpha);
12131229
});
12141230
const badPipeline = sharp({ raw: { width: 840, height: 500, channels: 3 } }).toFormat('jpeg');
12151231
readable.pipe(inPipeline).pipe(badPipeline);
@@ -1255,7 +1271,7 @@ suite('Input/output', () => {
12551271
.resize({ width: 8, height: 8 })
12561272
.toUint8Array();
12571273

1258-
t.plan(isMarkedAsUntransferable ? 12 : 11);
1274+
t.plan(isMarkedAsUntransferable ? 13 : 12);
12591275
t.assert.strictEqual(data instanceof Uint8Array, true);
12601276
if (isMarkedAsUntransferable) {
12611277
t.assert.strictEqual(isMarkedAsUntransferable(data.buffer), false);
@@ -1267,6 +1283,7 @@ suite('Input/output', () => {
12671283
t.assert.strictEqual(data.byteLength, info.size);
12681284
t.assert.strictEqual(data[0], 0xFF);
12691285
t.assert.strictEqual(data[1], 0xD8);
1286+
t.assert.strictEqual(info.hasAlpha, false);
12701287

12711288
const metadata = await sharp(data).metadata();
12721289
t.assert.strictEqual(metadata.format, 'jpeg');

0 commit comments

Comments
 (0)