summaryrefslogtreecommitdiff
path: root/packages/webapi/src/lib/ImageData.ts
blob: 595b5023fe5e62142549ba1d8465d5778b7b7354 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import * as _ from './utils'

export class ImageData {
	constructor(width: number, height: number)
	constructor(width: number, height: number, settings: ImageDataSettings)
	constructor(data: Uint8ClampedArray, width: number)
	constructor(data: Uint8ClampedArray, width: number, height: number)
	constructor(
		data: Uint8ClampedArray,
		width: number,
		height: number,
		settings: ImageDataSettings
	)

	constructor(
		arg0: number | Uint8ClampedArray,
		arg1: number,
		...args: [] | [number] | [ImageDataSettings] | [number, ImageDataSettings]
	) {
		if (arguments.length < 2)
			throw new TypeError(
				`Failed to construct 'ImageData': 2 arguments required.`
			)

		/** Whether Uint8ClampedArray data is provided. */
		const hasData = _.__object_isPrototypeOf(Uint8ClampedArray.prototype, arg0)

		/** Image data, either provided or calculated. */
		const d = hasData
			? (arg0 as Uint8ClampedArray)
			: new Uint8ClampedArray(
					asNumber(arg0, 'width') * asNumber(arg1, 'height') * 4
			  )

		/** Image width. */
		const w = asNumber(hasData ? arg1 : arg0, 'width')

		/** Image height. */
		const h = d.length / w / 4

		/** Image color space. */
		const c = String(
			Object(hasData ? args[1] : args[0]).colorSpace || 'srgb'
		) as PredefinedColorSpace

		// throw if a provided height does not match the calculated height
		if (args.length && asNumber(args[0], 'height') !== h)
			throw new DOMException(
				'height is not equal to (4 * width * height)',
				'IndexSizeError'
			)

		// throw if a provided colorspace does not match a known colorspace
		if (c !== 'srgb' && c !== 'rec2020' && c !== 'display-p3')
			throw new TypeError('colorSpace is not known value')

		Object.defineProperty(this, 'data', {
			configurable: true,
			enumerable: true,
			value: d,
		})

		_.INTERNALS.set(this, {
			width: w,
			height: h,
			colorSpace: c,
		} as ImageDataInternals)
	}

	get data(): Uint8ClampedArray {
		_.internalsOf<ImageDataInternals>(this, 'ImageData', 'data')

		return (
			Object.getOwnPropertyDescriptor(this, 'data') as {
				value: Uint8ClampedArray
			}
		).value
	}

	get width(): ImageDataInternals['width'] {
		return _.internalsOf<ImageDataInternals>(this, 'ImageData', 'width').width
	}

	get height(): ImageDataInternals['height'] {
		return _.internalsOf<ImageDataInternals>(this, 'ImageData', 'height').height
	}
}

_.allowStringTag(ImageData)

/** Returns a coerced number, optionally throwing if the number is zero-ish. */
const asNumber = (value: any, axis: string): number => {
	value = Number(value) || 0

	if (value === 0)
		throw new TypeError(`The source ${axis} is zero or not a number.`)

	return value
}

interface ImageDataInternals {
	colorSpace: PredefinedColorSpace
	height: number
	width: number
}

interface ImageDataSettings {
	colorSpace?: PredefinedColorSpace
}

type PredefinedColorSpace = 'srgb' | 'rec2020' | 'display-p3'