UNPKG

@itwin/core-backend

Version:
171 lines • 8.71 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { expect } from "chai"; import { imageBufferFromImageSource, imageSourceFromImageBuffer } from "../ImageSourceConversion"; import { samplePngTexture } from "./imageData"; import { ImageBuffer, ImageBufferFormat, ImageSourceFormat } from "@itwin/core-common"; // samplePngTexture encodes this image: // White Red Red // Red Blue Red // Red Red Green const red = 0x000000ff; const green = 0x0000ff00; const blue = 0x00ff0000; const white = 0x00ffffff; const top = [white, red, red]; const middle = [red, blue, red]; const bottom = [red, red, green]; const samplePng = { data: new Uint8Array(samplePngTexture.data), format: ImageSourceFormat.Png, }; function getPixel(img, x, y) { const pixelSize = img.numBytesPerPixel; const start = y * pixelSize * img.width + x * pixelSize; let pixel = 0; for (let offset = 0; offset < pixelSize; offset++) { let val = img.data[start + offset]; val = (val << (offset * 8)) >>> 0; pixel = (pixel | val) >>> 0; } return pixel; } function expectImagePixels(img, expected) { expect(img.width * img.height).to.equal(expected.length); const actual = []; for (let y = 0; y < img.height; y++) { for (let x = 0; x < img.width; x++) { actual.push(getPixel(img, x, y)); } } expect(actual).to.deep.equal(expected); } // Make an ImageBuffer as // White Red Red // Red Blue Red // Red Red Green // If an alpha channel is requested, alpha will be 0x7f function makeImage(wantAlpha) { const format = wantAlpha ? ImageBufferFormat.Rgba : ImageBufferFormat.Rgb; const pixelSize = wantAlpha ? 4 : 3; const data = new Uint8Array(pixelSize * 9); const lut = [white, red, red, red, blue, red, red, red, green]; for (let i = 0; i < 9; i++) { const value = lut[i]; const s = i * pixelSize; data[s + 0] = value & 0xff; data[s + 1] = (value >> 8) & 0xff; data[s + 2] = (value >> 16) & 0xff; if (wantAlpha) { data[s + 3] = 0x7f; } } const img = ImageBuffer.create(data, format, 3); return img; } function expectEqualImageBuffers(a, b, pixelTolerance = 0) { expect(a.format).to.equal(b.format); expect(a.width).to.equal(b.width); expect(a.height).to.equal(b.height); expect(a.data.length).to.equal(b.data.length); for (let i = 0; i < a.data.length; i++) { const x = a.data[i]; const y = b.data[i]; expect(Math.abs(x - y)).most(pixelTolerance); } } function computeMaxCompressionError(compressed, original) { expect(compressed.data.length).to.equal(original.data.length); let max = 0; for (let i = 0; i < compressed.data.length; i++) { max = Math.max(max, Math.abs(compressed.data[i] - original.data[i])); } return max; } describe("ImageSource conversion", () => { describe("imageBufferFromImageSource", () => { it("decodes PNG", () => { const buf = imageBufferFromImageSource({ source: samplePng }); expect(buf).not.to.be.undefined; expect(buf.width).to.equal(3); expect(buf.height).to.equal(3); expect(buf.format).to.equal(ImageBufferFormat.Rgb); expectImagePixels(buf, [...top, ...middle, ...bottom]); }); it("preserves alpha channel if RGBA requested", () => { const image = makeImage(true); const source = imageSourceFromImageBuffer({ image }); const result = imageBufferFromImageSource({ source, targetFormat: ImageBufferFormat.Rgba }); expect(result.format).to.equal(ImageBufferFormat.Rgba); expectImagePixels(result, [...top, ...middle, ...bottom].map((x) => (x | 0x7f000000) >>> 0)); }); it("strips alpha channel if RGB requested", () => { const image = makeImage(true); const source = imageSourceFromImageBuffer({ image }); const result = imageBufferFromImageSource({ source, targetFormat: ImageBufferFormat.Rgb }); expect(result.format).to.equal(ImageBufferFormat.Rgb); expectImagePixels(result, [...top, ...middle, ...bottom]); }); it("sets alpha to 255 if RGBA requested for ImageSource lacking transparency", () => { const img = imageBufferFromImageSource({ source: samplePng, targetFormat: ImageBufferFormat.Rgba }); expect(img.format).to.equal(ImageBufferFormat.Rgba); expectImagePixels(img, [...top, ...middle, ...bottom].map((x) => ((x | 0xff000000) >>> 0))); }); it("defaults to RGBA IFF alpha channel is present", () => { const transparent = imageSourceFromImageBuffer({ image: makeImage(true), targetFormat: ImageSourceFormat.Png }); expect(imageBufferFromImageSource({ source: transparent }).format).to.equal(ImageBufferFormat.Rgba); const opaque = imageSourceFromImageBuffer({ image: makeImage(false), targetFormat: ImageSourceFormat.Png }); expect(imageBufferFromImageSource({ source: opaque }).format).to.equal(ImageBufferFormat.Rgb); }); }); describe("imageSourceFromImageBuffer", () => { it("encodes to specified format", () => { const image = makeImage(false); const png = imageSourceFromImageBuffer({ image, targetFormat: ImageSourceFormat.Png }); expect(png.format).to.equal(ImageSourceFormat.Png); const jpeg = imageSourceFromImageBuffer({ image, targetFormat: ImageSourceFormat.Jpeg }); expect(jpeg.format).to.equal(ImageSourceFormat.Jpeg); // PNG is lossless expectEqualImageBuffers(imageBufferFromImageSource({ source: png }), image); // JPEG is lossy expectEqualImageBuffers(imageBufferFromImageSource({ source: jpeg }), image, 2); }); it("defaults to PNG IFF alpha channel is present", () => { const transparent = makeImage(true); expect(imageSourceFromImageBuffer({ image: transparent }).format).to.equal(ImageSourceFormat.Png); const opaque = makeImage(false); expect(imageSourceFromImageBuffer({ image: opaque }).format).to.equal(ImageSourceFormat.Jpeg); }); it("flips vertically IFF specified", () => { const image = makeImage(false); let source = imageSourceFromImageBuffer({ image, targetFormat: ImageSourceFormat.Png }); let output = imageBufferFromImageSource({ source }); expectImagePixels(output, [...top, ...middle, ...bottom]); source = imageSourceFromImageBuffer({ image, targetFormat: ImageSourceFormat.Png, flipVertically: true }); output = imageBufferFromImageSource({ source }); expectImagePixels(output, [...bottom, ...middle, ...top]); }); it("trades quality for size when encoding JPEG", () => { const image = makeImage(false); const low = imageSourceFromImageBuffer({ image, targetFormat: ImageSourceFormat.Jpeg, jpegQuality: 50 }); const medium = imageSourceFromImageBuffer({ image, targetFormat: ImageSourceFormat.Jpeg, jpegQuality: 75 }); const high = imageSourceFromImageBuffer({ image, targetFormat: ImageSourceFormat.Jpeg, jpegQuality: 100 }); expect(low.data.length).lessThan(medium.data.length); expect(medium.data.length).lessThan(high.data.length); const highImg = imageBufferFromImageSource({ source: high }); const lowImg = imageBufferFromImageSource({ source: low }); const mediumImg = imageBufferFromImageSource({ source: medium }); expect(computeMaxCompressionError(highImg, image)).to.equal(2); const mediumError = computeMaxCompressionError(mediumImg, image); expect(mediumError).greaterThan(2); expect(computeMaxCompressionError(lowImg, image)).greaterThan(mediumError); }); it("throws if image is in alpha format", () => { const image = ImageBuffer.create(new Uint8Array([1, 2, 3, 4, 5]), ImageBufferFormat.Alpha, 5); expect(() => imageSourceFromImageBuffer({ image })).to.throw("imageSourceFromImageBuffer cannot be used with alpha-only images"); }); }); }); //# sourceMappingURL=ImageSourceConversion.test.js.map