|
1 | 1 | 'use strict'; |
2 | 2 |
|
| 3 | +const magick = require('@imagemagick/magick-wasm'); |
| 4 | +const fs = require('fs'); |
3 | 5 | const ImageBase = require('../image-base'); |
| 6 | +const {RGB_IMAGE_CHANNELS, BITS_IN_BYTE} = require('../constants'); |
| 7 | + |
| 8 | +const supportedFormats = ['JPEG', 'PNG', 'WEBP', 'GIF', 'AVIF', 'TIFF', 'SVG']; |
4 | 9 |
|
5 | 10 | module.exports = class Image extends ImageBase { |
6 | | - constructor(img) { |
| 11 | + constructor(buffer, rgb) { |
7 | 12 | super(); |
8 | 13 |
|
9 | | - this._img = img; |
10 | | - } |
| 14 | + let settings; |
| 15 | + |
| 16 | + if (rgb) { |
| 17 | + settings = new magick.MagickReadSettings({format: 'RGB', width: rgb.width, height: rgb.height}); |
| 18 | + } |
11 | 19 |
|
12 | | - async init() { |
13 | | - const {data, info} = await this._img.raw().toBuffer({resolveWithObject: true}); |
| 20 | + this._img = magick.MagickImage.create(buffer, settings); |
14 | 21 |
|
15 | | - this._buffer = data; |
16 | | - this._width = info.width; |
17 | | - this._height = info.height; |
18 | | - this._channels = info.channels; |
| 22 | + this._width = this._img.width; |
| 23 | + this._height = this._img.height; |
19 | 24 | } |
20 | 25 |
|
21 | | - async initMeta() { |
22 | | - const {width, height, channels} = await this._img.metadata(); |
| 26 | + /** |
| 27 | + * @returns 8 bit depth UInt8Array of rgb colors |
| 28 | + */ |
| 29 | + _getRgbBuffer() { |
| 30 | + if (this._rgbBuffer) { |
| 31 | + return this._rgbBuffer; |
| 32 | + } |
| 33 | + |
| 34 | + const imageDepth = this._img.depth; |
| 35 | + |
| 36 | + this._img.depth = BITS_IN_BYTE; |
| 37 | + this._rgbBuffer = this._img.write('RGB', Buffer.from); |
| 38 | + this._img.depth = imageDepth; |
23 | 39 |
|
24 | | - this._width = width; |
25 | | - this._height = height; |
26 | | - this._channels = channels; |
| 40 | + return this._rgbBuffer; |
27 | 41 | } |
28 | 42 |
|
29 | 43 | getPixel(x, y) { |
30 | | - const idx = this._getIdx(x, y); |
| 44 | + const rgbBuffer = this._getRgbBuffer(); |
| 45 | + const idx = (this._width * y + x) * RGB_IMAGE_CHANNELS; |
| 46 | + |
31 | 47 | return { |
32 | | - R: this._buffer[idx], |
33 | | - G: this._buffer[idx + 1], |
34 | | - B: this._buffer[idx + 2] |
| 48 | + R: rgbBuffer[idx], |
| 49 | + G: rgbBuffer[idx + 1], |
| 50 | + B: rgbBuffer[idx + 2] |
35 | 51 | }; |
36 | 52 | } |
37 | 53 |
|
38 | | - _getIdx(x, y) { |
39 | | - return (this._width * y + x) * this._channels; |
40 | | - } |
41 | | - |
42 | 54 | async save(path) { |
43 | | - return this._img.toFile(path); |
| 55 | + const dotIndex = path.lastIndexOf('.'); |
| 56 | + |
| 57 | + let imageExtension = 'PNG'; |
| 58 | + |
| 59 | + if (dotIndex !== -1) { |
| 60 | + const fileExtension = path.slice(dotIndex + 1).toUpperCase(); |
| 61 | + |
| 62 | + if (supportedFormats.includes(fileExtension)) { |
| 63 | + imageExtension = fileExtension; |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + return this._img.write(imageExtension, buffer => fs.promises.writeFile(path, buffer)); |
44 | 68 | } |
45 | 69 |
|
46 | 70 | async createBuffer(extension) { |
47 | | - return this._img.toFormat(extension).toBuffer(); |
| 71 | + if (extension === 'raw') { |
| 72 | + return this._getRgbBuffer(); |
| 73 | + } |
| 74 | + |
| 75 | + return this._img.write('PNG', Buffer.from); |
48 | 76 | } |
49 | 77 | }; |
| 78 | + |
| 79 | +let initMagickPromise; |
| 80 | + |
| 81 | +module.exports.initMagick = () => { |
| 82 | + if (initMagickPromise) { |
| 83 | + return initMagickPromise; |
| 84 | + } |
| 85 | + |
| 86 | + return initMagickPromise = new Promise(resolve => { |
| 87 | + const magickWasmBytesLocation = require.resolve('@imagemagick/magick-wasm/magick.wasm'); |
| 88 | + |
| 89 | + return fs.promises.readFile(magickWasmBytesLocation).then(magick.initializeImageMagick).then(resolve); |
| 90 | + }); |
| 91 | +}; |
0 commit comments