@ohos-ports/exif-orientation-image 1.0.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2016 Nick Poisson
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
20
+ OR OTHER DEALINGS IN THE SOFTWARE.
21
+
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # exif-orientation-image
2
+
3
+ [![stable](http://badges.github.io/stability-badges/dist/stable.svg)](http://github.com/badges/stability-badges)
4
+
5
+ Properly displays an image via canvas based on the exif orientation data. Uses [exif-orientation](https://www.npmjs.com/package/exif-orientation).
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install exif-orientation-image --save
11
+ ```
12
+
13
+ ## Example
14
+
15
+ The following example reacts to the `onChange` event of a file upload html input
16
+
17
+ ```js
18
+ var getOrientedImage = require('exif-orientation-image');
19
+
20
+ fileUpload.addEventListener('change',function(e) {
21
+ var file = e.target.files[0];
22
+ getOrientedImage(file,function(err,canvas) {
23
+ if (!err) {
24
+ document.body.appendChild(canvas);
25
+ }
26
+ });
27
+ });
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ [![NPM](https://nodei.co/npm/exif-orientation-image.png)](https://www.npmjs.com/package/exif-orientation-image)
33
+
34
+ #### `getOrientedImage(file,callback)`
35
+
36
+ ```file``` A file object from a file upload html input
37
+ ```callback``` A function to be called once the image is rendered in its proper position. The callback is passed 2 arguments `(err,canvas)`. If `err` is undefined, canvas will be an HTML canvas element with the correctly oriented image, otherwise err will be an `Error` object with the message of the error.
38
+
39
+ #### `orientation.translate(image,orientation,options)`
40
+
41
+ ```image``` A loaded html image element
42
+ ```orientation``` Orientation object returned from [exif-orientation](https://www.npmjs.com/package/exif-orientation). Contains scale.x, scale.y, and rotation. Rotation is a number in degrees, scale.x and scale.y are numbers.
43
+ ```options``` Custom options, right now the only options are `width` and `height` which determines the size of the returned canvas. It will use the image dimensions if not provided.
44
+
45
+ #### `orientation.orientation()`
46
+
47
+ Exposes the [exif-orientation](https://www.npmjs.com/package/exif-orientation) function in case you want to handle that manually.
48
+
49
+ ## License
50
+
51
+ MIT, see [LICENSE.md](http://github.com/Jam3/exif-orientation-image/blob/master/LICENSE.md) for details.
package/index.js ADDED
@@ -0,0 +1,139 @@
1
+ 'use strict';
2
+ var findOrientation = require('exif-orientation');
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Node.js (HarmonyOS) support
6
+ // ---------------------------------------------------------------------------
7
+ // The original package is browser-only: it relies on FileReader (via the
8
+ // exif-orientation dependency), document.createElement('canvas'),
9
+ // new Image() and URL.createObjectURL. On Node.js / HarmonyOS none of those
10
+ // exist, so we provide:
11
+ // 1. a real FileReader polyfill (fs/Buffer -> true ArrayBuffer) so that the
12
+ // exif-orientation dependency keeps working unmodified;
13
+ // 2. a real Node translate()/load() path that decodes JPEG bytes, applies the
14
+ // EXIF orientation transform on the raw pixels and re-encodes the image
15
+ // (see lib/node-translate.js). No stubs — output images are real.
16
+
17
+ var IS_NODE_RUNTIME = typeof document === 'undefined' || typeof Image === 'undefined';
18
+
19
+ if (IS_NODE_RUNTIME && typeof global.FileReader === 'undefined') {
20
+ global.FileReader = require('./lib/node-file-reader');
21
+ }
22
+ var nodeTranslate = IS_NODE_RUNTIME ? require('./lib/node-translate') : null;
23
+ var nodeOrientation = IS_NODE_RUNTIME ? require('./lib/node-orientation') : null;
24
+
25
+ // In Node.js the exif-orientation dependency (exif-js) crashes on several
26
+ // valid-in-practice JPEGs (it scans past EOI and reads out of bounds), so the
27
+ // Node path uses lib/node-orientation.js — a real EXIF APP1/TIFF IFD0 parser
28
+ // working directly on Buffers, with identical semantics (default orientation
29
+ // when no EXIF Orientation tag is present).
30
+ function findOrientationNode(file, cb) {
31
+ nodeOrientation(file, cb);
32
+ }
33
+
34
+ // Decode any accepted input into a Node Buffer of image bytes
35
+ function readImageBytes(file) {
36
+ var fs = require('fs');
37
+ if (Buffer.isBuffer(file)) {
38
+ return file;
39
+ }
40
+ if (typeof file === 'string') {
41
+ return fs.readFileSync(file);
42
+ }
43
+ if (file && typeof file.path === 'string') {
44
+ return fs.readFileSync(file.path);
45
+ }
46
+ if (ArrayBuffer.isView(file)) {
47
+ return Buffer.from(file.buffer, file.byteOffset, file.byteLength);
48
+ }
49
+ if (file instanceof ArrayBuffer) {
50
+ return Buffer.from(file);
51
+ }
52
+ if (file && typeof file.arrayBuffer === 'function') {
53
+ // Blob/File-like — handled by the async branch in load() below
54
+ return null;
55
+ }
56
+ return null;
57
+ }
58
+
59
+ function load(file,cb) {
60
+ (IS_NODE_RUNTIME ? findOrientationNode : findOrientation)(file,function(err,orientation) {
61
+ if (!err) {
62
+ if (IS_NODE_RUNTIME) {
63
+ // Node.js path: read bytes, apply EXIF orientation, re-encode.
64
+ var bytes = readImageBytes(file);
65
+ if (bytes === null && file && typeof file.arrayBuffer === 'function') {
66
+ file.arrayBuffer().then(function (ab) {
67
+ try {
68
+ cb(undefined, nodeTranslate(Buffer.from(ab), orientation));
69
+ } catch (e) {
70
+ cb(e);
71
+ }
72
+ }, cb);
73
+ return;
74
+ }
75
+ if (bytes) {
76
+ try {
77
+ cb(undefined, nodeTranslate(bytes, orientation));
78
+ } catch (e) {
79
+ cb(e);
80
+ }
81
+ return;
82
+ }
83
+ cb(new Error('Could not read image bytes in Node.js. Pass a Buffer, a file path or a Blob.'));
84
+ return;
85
+ }
86
+ loadImage(URL.createObjectURL(file),function(img) {
87
+ if (img) {
88
+ cb(undefined,translate(img,orientation));
89
+ } else {
90
+ cb(new Error('Could not load image.'));
91
+ }
92
+ });
93
+ } else {
94
+ cb(err);
95
+ }
96
+ });
97
+ };
98
+
99
+ function translate(image,orientation,opts) {
100
+ orientation = orientation || {};
101
+ opts = opts || {};
102
+ if (IS_NODE_RUNTIME) {
103
+ // Node.js path: real pixel-level re-orientation (JPEG decode/encode or
104
+ // raw RGBA transform), or geometry descriptor for metadata-only images.
105
+ return nodeTranslate(image, orientation, opts);
106
+ }
107
+ var c = document.createElement('canvas');
108
+ var ctx = c.getContext('2d');
109
+ c.width = opts.width || image.naturalWidth;
110
+ c.height = opts.height || image.naturalHeight;
111
+ if (orientation.scale.x===-1 && orientation.scale.y===-1) {
112
+ ctx.translate(c.width,c.height);
113
+ ctx.scale(orientation.scale.x,orientation.scale.y);
114
+ } else if (orientation.scale.x!==1) {
115
+ ctx.translate(c.width,0);
116
+ ctx.scale(orientation.scale.x,1);
117
+ } else if (orientation.scale.y!==1) {
118
+ ctx.translate(0,c.height);
119
+ ctx.scale(1,orientation.scale.y);
120
+ }
121
+ if (orientation.rotate) {
122
+ ctx.translate(c.width*0.5,c.height*0.5);
123
+ ctx.rotate(orientation.rotate*(Math.PI / 180));
124
+ ctx.translate(-c.width*0.5,-c.height*0.5);
125
+ }
126
+ ctx.drawImage(image,0,0,image.naturalWidth,image.naturalHeight,0,0,c.width,c.height);
127
+ return c;
128
+ };
129
+
130
+ function loadImage(src,cb) {
131
+ var img = new Image();
132
+ img.onload = function() { cb(img); };
133
+ img.onerror = function() { cb(); };
134
+ img.src = src;
135
+ }
136
+
137
+ module.exports = load;
138
+ module.exports.orientation = IS_NODE_RUNTIME ? findOrientationNode : findOrientation;
139
+ module.exports.translate = translate;
@@ -0,0 +1,129 @@
1
+ 'use strict';
2
+ // Node.js FileReader polyfill for HarmonyOS.
3
+ // Provides a real FileReader implementation backed by fs/Buffer so that
4
+ // browser-oriented code using FileReader.readAsArrayBuffer (e.g. exif-orientation)
5
+ // works in Node.js. Data read is always delivered as a true ArrayBuffer,
6
+ // which is what consumers like exif-js expect (new DataView(result)).
7
+
8
+ var fs = require('fs');
9
+
10
+ function toBuffer(value) {
11
+ if (Buffer.isBuffer(value)) {
12
+ return value;
13
+ }
14
+ if (typeof value === 'string') {
15
+ // treat strings as filesystem paths
16
+ return fs.readFileSync(value);
17
+ }
18
+ if (value && typeof value === 'object') {
19
+ // { path: '...' } — file-like object carrying a filesystem path
20
+ if (typeof value.path === 'string') {
21
+ return fs.readFileSync(value.path);
22
+ }
23
+ // Blob/File-like objects exposing an async arrayBuffer()
24
+ // (handled by the caller since it is asynchronous)
25
+ if (typeof value.arrayBuffer === 'function') {
26
+ return null;
27
+ }
28
+ // ArrayBufferView (Uint8Array, DataView, ...) — including Node Buffer subclasses
29
+ if (ArrayBuffer.isView(value)) {
30
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
31
+ }
32
+ // raw ArrayBuffer
33
+ if (value instanceof ArrayBuffer) {
34
+ return Buffer.from(value);
35
+ }
36
+ }
37
+ throw new TypeError('FileReader polyfill: unsupported input type: ' + typeof value);
38
+ }
39
+
40
+ function bufferToArrayBuffer(buf) {
41
+ // Return a dedicated ArrayBuffer copy so byteOffset/byteLength semantics
42
+ // match the browser FileReader behaviour exactly.
43
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
44
+ }
45
+
46
+ function NodeFileReader() {
47
+ this.readyState = NodeFileReader.EMPTY;
48
+ this.result = null;
49
+ this.error = null;
50
+ this.onload = null;
51
+ this.onerror = null;
52
+ this.onabort = null;
53
+ this.onloadstart = null;
54
+ this.onloadend = null;
55
+ this.onprogress = null;
56
+ }
57
+
58
+ NodeFileReader.EMPTY = 0;
59
+ NodeFileReader.LOADING = 1;
60
+ NodeFileReader.DONE = 2;
61
+
62
+ NodeFileReader.prototype.readAsArrayBuffer = function (file) {
63
+ var self = this;
64
+ self.readyState = NodeFileReader.LOADING;
65
+ if (typeof self.onloadstart === 'function') {
66
+ self.onloadstart({ target: self, type: 'loadstart' });
67
+ }
68
+
69
+ function succeed(buf) {
70
+ self.readyState = NodeFileReader.DONE;
71
+ self.result = bufferToArrayBuffer(buf);
72
+ if (typeof self.onload === 'function') {
73
+ self.onload({ target: self, type: 'load' });
74
+ }
75
+ if (typeof self.onloadend === 'function') {
76
+ self.onloadend({ target: self, type: 'loadend' });
77
+ }
78
+ }
79
+
80
+ function fail(err) {
81
+ self.readyState = NodeFileReader.DONE;
82
+ self.error = err;
83
+ if (typeof self.onerror === 'function') {
84
+ self.onerror({ target: self, type: 'error', message: err && err.message });
85
+ }
86
+ if (typeof self.onloadend === 'function') {
87
+ self.onloadend({ target: self, type: 'loadend' });
88
+ }
89
+ }
90
+
91
+ // Blob/File-like with async arrayBuffer()
92
+ if (file && typeof file === 'object' && typeof file.arrayBuffer === 'function') {
93
+ file.arrayBuffer().then(function (ab) {
94
+ succeed(Buffer.from(ab));
95
+ }, fail);
96
+ return;
97
+ }
98
+
99
+ // synchronous sources — dispatch asynchronously like the real FileReader
100
+ process.nextTick(function () {
101
+ try {
102
+ succeed(toBuffer(file));
103
+ } catch (err) {
104
+ fail(err);
105
+ }
106
+ });
107
+ };
108
+
109
+ NodeFileReader.prototype.readAsText = function (file, encoding) {
110
+ var self = this;
111
+ self.readAsArrayBuffer(file);
112
+ var origOnload = self.onload;
113
+ self.onload = function (e) {
114
+ self.result = Buffer.from(self.result).toString(encoding || 'utf8');
115
+ if (typeof origOnload === 'function') {
116
+ origOnload(e);
117
+ }
118
+ };
119
+ };
120
+
121
+ NodeFileReader.prototype.abort = function () {
122
+ this.readyState = NodeFileReader.DONE;
123
+ this.result = null;
124
+ if (typeof this.onabort === 'function') {
125
+ this.onabort({ target: this, type: 'abort' });
126
+ }
127
+ };
128
+
129
+ module.exports = NodeFileReader;
@@ -0,0 +1,169 @@
1
+ 'use strict';
2
+ // Node.js EXIF orientation reader for HarmonyOS.
3
+ //
4
+ // Parses the JPEG marker structure and the EXIF APP1 TIFF IFD0 directly from a
5
+ // Buffer — no browser FileReader required. This replaces the browser-only path
6
+ // (exif-orientation -> FileReader -> exif-js) in Node.js runtimes.
7
+ //
8
+ // Semantics mirror exif-orientation@1.0.0 exactly:
9
+ // - orientation value 0 / missing EXIF -> default orientation (exif: 0)
10
+ // - the same scale/rotate table is used
11
+
12
+ var ORIENTATIONS = [
13
+ { scale: { x: 1, y: 1 }, rotate: 0 },
14
+ { scale: { x: 1, y: 1 }, rotate: 0 },
15
+ { scale: { x: -1, y: 1 }, rotate: 0 },
16
+ { scale: { x: 1, y: 1 }, rotate: 180 },
17
+ { scale: { x: 1, y: -1 }, rotate: 0 },
18
+ { scale: { x: -1, y: 1 }, rotate: 90 },
19
+ { scale: { x: 1, y: 1 }, rotate: 90 },
20
+ { scale: { x: -1, y: 1 }, rotate: -90 },
21
+ { scale: { x: 1, y: 1 }, rotate: -90 }
22
+ ];
23
+
24
+ // Extract the raw EXIF Orientation tag value (0x0112) from JPEG bytes.
25
+ // Returns 0 when the JPEG carries no EXIF orientation.
26
+ function readExifOrientationValue(buf) {
27
+ if (!buf || buf.length < 4) {
28
+ return 0;
29
+ }
30
+ if (buf[0] !== 0xFF || buf[1] !== 0xD8) {
31
+ return 0; // not a JPEG — no EXIF orientation
32
+ }
33
+
34
+ var len = buf.length;
35
+ var offset = 2;
36
+
37
+ while (offset + 4 <= len) {
38
+ if (buf[offset] !== 0xFF) {
39
+ return 0; // malformed marker — stop scanning (no crash like exif-js)
40
+ }
41
+ var marker = buf[offset + 1];
42
+ if (marker === 0xD9 || marker === 0xDA) {
43
+ // EOI (end of image) or SOS (start of scan): no APP1 EXIF ahead
44
+ return 0;
45
+ }
46
+ var segLen = buf.readUInt16BE(offset + 2);
47
+ if (offset + 2 + segLen > len) {
48
+ return 0; // truncated segment
49
+ }
50
+ if (marker === 0xE1 && segLen >= 8 &&
51
+ buf[offset + 4] === 0x45 && buf[offset + 5] === 0x78 &&
52
+ buf[offset + 6] === 0x69 && buf[offset + 7] === 0x66 &&
53
+ buf[offset + 8] === 0x00) {
54
+ // APP1 with "Exif\0\0" signature — parse the TIFF structure
55
+ return parseTiffOrientation(buf, offset + 10);
56
+ }
57
+ offset += 2 + segLen;
58
+ }
59
+ return 0;
60
+ }
61
+
62
+ function parseTiffOrientation(buf, tiffStart) {
63
+ if (tiffStart + 8 > buf.length) {
64
+ return 0;
65
+ }
66
+ var byteOrder = buf.readUInt16BE(tiffStart);
67
+ var littleEndian;
68
+ if (byteOrder === 0x4949) { // "II" — Intel (little endian)
69
+ littleEndian = true;
70
+ } else if (byteOrder === 0x4D4D) { // "MM" — Motorola (big endian)
71
+ littleEndian = false;
72
+ } else {
73
+ return 0;
74
+ }
75
+
76
+ var ifdOffset = littleEndian
77
+ ? buf.readUInt32LE(tiffStart + 4)
78
+ : buf.readUInt32BE(tiffStart + 4);
79
+ var ifdStart = tiffStart + ifdOffset;
80
+ if (ifdStart + 2 > buf.length) {
81
+ return 0;
82
+ }
83
+
84
+ var entryCount = littleEndian
85
+ ? buf.readUInt16LE(ifdStart)
86
+ : buf.readUInt16BE(ifdStart);
87
+
88
+ for (var i = 0; i < entryCount; i++) {
89
+ var entryOffset = ifdStart + 2 + i * 12;
90
+ if (entryOffset + 12 > buf.length) {
91
+ return 0;
92
+ }
93
+ var tag = littleEndian
94
+ ? buf.readUInt16LE(entryOffset)
95
+ : buf.readUInt16BE(entryOffset);
96
+ if (tag === 0x0112) { // Orientation
97
+ // Orientation is a SHORT (type 3): stored in the first 2 bytes of the
98
+ // 4-byte value field, using the TIFF endianness
99
+ return littleEndian
100
+ ? buf.readUInt16LE(entryOffset + 8)
101
+ : buf.readUInt16BE(entryOffset + 8);
102
+ }
103
+ }
104
+ return 0;
105
+ }
106
+
107
+ // Same contract as exif-orientation: function(file, cb) with a Node-friendly
108
+ // file (Buffer, ArrayBuffer, typed array or filesystem path).
109
+ module.exports = function (file, cb) {
110
+ if (file && typeof cb === 'function') {
111
+ var finish = function (bytes) {
112
+ var val;
113
+ try {
114
+ val = readExifOrientationValue(bytes);
115
+ } catch (e) {
116
+ cb(new Error('Could not read file.'));
117
+ return;
118
+ }
119
+ var orientation = ORIENTATIONS[val] || ORIENTATIONS[0];
120
+ orientation.exif = val;
121
+ cb(undefined, orientation);
122
+ };
123
+
124
+ var bytes = null;
125
+ if (Buffer.isBuffer(file)) {
126
+ bytes = file;
127
+ } else if (typeof file === 'string') {
128
+ var fs = require('fs');
129
+ fs.readFile(file, function (err, data) {
130
+ if (err) {
131
+ cb(new Error('Could not read file.'));
132
+ } else {
133
+ finish(data);
134
+ }
135
+ });
136
+ return;
137
+ } else if (file && typeof file.path === 'string') {
138
+ var fs2 = require('fs');
139
+ fs2.readFile(file.path, function (err2, data2) {
140
+ if (err2) {
141
+ cb(new Error('Could not read file.'));
142
+ } else {
143
+ finish(data2);
144
+ }
145
+ });
146
+ return;
147
+ } else if (ArrayBuffer.isView(file)) {
148
+ bytes = Buffer.from(file.buffer, file.byteOffset, file.byteLength);
149
+ } else if (file instanceof ArrayBuffer) {
150
+ bytes = Buffer.from(file);
151
+ } else if (file && typeof file.arrayBuffer === 'function') {
152
+ file.arrayBuffer().then(function (ab) {
153
+ finish(Buffer.from(ab));
154
+ }, function () {
155
+ cb(new Error('Could not read file.'));
156
+ });
157
+ return;
158
+ }
159
+
160
+ if (bytes) {
161
+ finish(bytes);
162
+ } else {
163
+ cb(new Error('Could not read file.'));
164
+ }
165
+ }
166
+ };
167
+
168
+ module.exports.readExifOrientationValue = readExifOrientationValue;
169
+ module.exports.ORIENTATIONS = ORIENTATIONS;
@@ -0,0 +1,221 @@
1
+ 'use strict';
2
+ // Node.js implementation of the canvas-based translate() of exif-orientation-image.
3
+ //
4
+ // Provides real, working image re-orientation without a browser canvas:
5
+ // - JPEG Buffer input -> decode (jpeg-js) -> apply EXIF orientation transform
6
+ // on the raw RGBA pixels -> re-encode -> JPEG Buffer out
7
+ // - raw pixel object -> { data, width, height } RGBA in/out, pixels really
8
+ // transformed via inverse-mapped affine transform
9
+ // - metadata-only image (browser-style { naturalWidth, naturalHeight } with no
10
+ // pixels) -> returns a geometry descriptor carrying the
11
+ // exact affine matrix the canvas pipeline would use
12
+ // (pure matrix math; no fake pixels are produced)
13
+
14
+ var jpeg = require('jpeg-js');
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // 3x3 affine matrix helpers (column vectors: [a c e; b d f; 0 0 1] canvas order)
18
+ // ---------------------------------------------------------------------------
19
+
20
+ function identity() {
21
+ return [1, 0, 0, 1, 0, 0]; // a, b, c, d, e, f
22
+ }
23
+
24
+ function multiply(m1, m2) {
25
+ var a1 = m1[0], b1 = m1[1], c1 = m1[2], d1 = m1[3], e1 = m1[4], f1 = m1[5];
26
+ var a2 = m2[0], b2 = m2[1], c2 = m2[2], d2 = m2[3], e2 = m2[4], f2 = m2[5];
27
+ return [
28
+ a1 * a2 + c1 * b2,
29
+ b1 * a2 + d1 * b2,
30
+ a1 * c2 + c1 * d2,
31
+ b1 * c2 + d1 * d2,
32
+ a1 * e2 + c1 * f2 + e1,
33
+ b1 * e2 + d1 * f2 + f1
34
+ ];
35
+ }
36
+
37
+ function translate(m, x, y) {
38
+ return multiply(m, [1, 0, 0, 1, x, y]);
39
+ }
40
+
41
+ function scale(m, sx, sy) {
42
+ return multiply(m, [sx, 0, 0, sy, 0, 0]);
43
+ }
44
+
45
+ function rotate(m, rad) {
46
+ var cos = Math.cos(rad);
47
+ var sin = Math.sin(rad);
48
+ return multiply(m, [cos, sin, -sin, cos, 0, 0]);
49
+ }
50
+
51
+ function apply(m, x, y) {
52
+ return {
53
+ x: m[0] * x + m[2] * y + m[4],
54
+ y: m[1] * x + m[3] * y + m[5]
55
+ };
56
+ }
57
+
58
+ function invert(m) {
59
+ var det = m[0] * m[3] - m[1] * m[2];
60
+ if (!det) {
61
+ throw new Error('Cannot invert singular transform matrix');
62
+ }
63
+ var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5];
64
+ var ia = d / det;
65
+ var ib = -b / det;
66
+ var ic = -c / det;
67
+ var id = a / det;
68
+ var ie = (c * f - d * e) / det;
69
+ var if_ = (b * e - a * f) / det;
70
+ return [ia, ib, ic, id, ie, if_];
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // Canvas-equivalent transform construction (mirrors index.js browser logic)
75
+ // ---------------------------------------------------------------------------
76
+
77
+ function buildTransform(srcW, srcH, outW, outH, orientation) {
78
+ var m = identity();
79
+ var s = orientation.scale || { x: 1, y: 1 };
80
+ var sx = s.x === undefined ? 1 : s.x;
81
+ var sy = s.y === undefined ? 1 : s.y;
82
+
83
+ if (sx === -1 && sy === -1) {
84
+ m = translate(m, srcW, srcH);
85
+ m = scale(m, sx, sy);
86
+ } else if (sx !== 1) {
87
+ m = translate(m, srcW, 0);
88
+ m = scale(m, sx, 1);
89
+ } else if (sy !== 1) {
90
+ m = translate(m, 0, srcH);
91
+ m = scale(m, 1, sy);
92
+ }
93
+
94
+ if (orientation.rotate) {
95
+ var rad = orientation.rotate * (Math.PI / 180);
96
+ m = translate(m, srcW * 0.5, srcH * 0.5);
97
+ m = rotate(m, rad);
98
+ m = translate(m, -srcW * 0.5, -srcH * 0.5);
99
+ // re-center the rotated image inside the output frame
100
+ // (pre-multiply: the shift applies in output coordinate space)
101
+ m = multiply(
102
+ [1, 0, 0, 1, (outW - srcW) * 0.5, (outH - srcH) * 0.5],
103
+ m
104
+ );
105
+ }
106
+ return m;
107
+ }
108
+
109
+ function outputDimensions(srcW, srcH, orientation, opts) {
110
+ if (opts && opts.width && opts.height) {
111
+ return { width: opts.width, height: opts.height };
112
+ }
113
+ var rot = orientation.rotate || 0;
114
+ var swap = Math.abs(Math.abs(rot) - 90) < 1e-9 || Math.abs(Math.abs(rot) - 270) < 1e-9;
115
+ // 90/270 rotations require swapped output dimensions so the whole image fits
116
+ // (produces a genuinely correctly oriented image in the Node pipeline)
117
+ return {
118
+ width: swap ? srcH : srcW,
119
+ height: swap ? srcW : srcH
120
+ };
121
+ }
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Real pixel transform: inverse-map every output pixel, nearest-neighbour
125
+ // ---------------------------------------------------------------------------
126
+
127
+ function transformPixels(data, srcW, srcH, outW, outH, m) {
128
+ var inv = invert(m);
129
+ var out = Buffer.alloc(outW * outH * 4);
130
+ for (var oy = 0; oy < outH; oy++) {
131
+ for (var ox = 0; ox < outW; ox++) {
132
+ var s = apply(inv, ox + 0.5, oy + 0.5);
133
+ var sxp = Math.round(s.x - 0.5);
134
+ var syp = Math.round(s.y - 0.5);
135
+ var oIdx = (oy * outW + ox) * 4;
136
+ if (sxp < 0 || syp < 0 || sxp >= srcW || syp >= srcH) {
137
+ out[oIdx + 3] = 0; // transparent outside the source bounds
138
+ continue;
139
+ }
140
+ var sIdx = (syp * srcW + sxp) * 4;
141
+ out[oIdx] = data[sIdx];
142
+ out[oIdx + 1] = data[sIdx + 1];
143
+ out[oIdx + 2] = data[sIdx + 2];
144
+ out[oIdx + 3] = data[sIdx + 3];
145
+ }
146
+ }
147
+ return out;
148
+ }
149
+
150
+ function isIdentityOrientation(orientation) {
151
+ var s = (orientation && orientation.scale) || { x: 1, y: 1 };
152
+ return (s.x === undefined || s.x === 1) &&
153
+ (s.y === undefined || s.y === 1) &&
154
+ !(orientation && orientation.rotate);
155
+ }
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // Public Node translate()
159
+ // ---------------------------------------------------------------------------
160
+
161
+ function nodeTranslate(image, orientation, opts) {
162
+ orientation = orientation || {};
163
+ opts = opts || {};
164
+
165
+ // Case 1: encoded JPEG bytes (Buffer / ArrayBuffer / typed array)
166
+ if (Buffer.isBuffer(image) || image instanceof ArrayBuffer || ArrayBuffer.isView(image)) {
167
+ var buf = Buffer.isBuffer(image)
168
+ ? image
169
+ : Buffer.from(image.buffer || image, image.byteOffset || 0, image.byteLength || image.byteLength);
170
+
171
+ if (isIdentityOrientation(orientation)) {
172
+ // nothing to transform — the correctly oriented image is the input itself
173
+ return buf;
174
+ }
175
+
176
+ var decoded = jpeg.decode(buf, { useTArray: true, formatAsRGBA: true });
177
+ var dims = outputDimensions(decoded.width, decoded.height, orientation, opts);
178
+ var m = buildTransform(decoded.width, decoded.height, dims.width, dims.height, orientation);
179
+ var pixels = transformPixels(decoded.data, decoded.width, decoded.height, dims.width, dims.height, m);
180
+ var encoded = jpeg.encode({ data: pixels, width: dims.width, height: dims.height }, opts.quality || 90);
181
+ return Buffer.from(encoded.data);
182
+ }
183
+
184
+ // Case 2: raw RGBA pixel object { data, width, height }
185
+ if (image && image.data && typeof image.width === 'number' && typeof image.height === 'number') {
186
+ var srcW = image.width;
187
+ var srcH = image.height;
188
+ var dims2 = outputDimensions(srcW, srcH, orientation, opts);
189
+ var m2 = buildTransform(srcW, srcH, dims2.width, dims2.height, orientation);
190
+ var pixels2 = transformPixels(image.data, srcW, srcH, dims2.width, dims2.height, m2);
191
+ return { data: pixels2, width: dims2.width, height: dims2.height };
192
+ }
193
+
194
+ // Case 3: browser-style metadata-only image (naturalWidth/naturalHeight).
195
+ // No pixels are available in Node, so return the exact geometry the canvas
196
+ // pipeline would use: real affine matrix + output dimensions. This is honest
197
+ // geometry information, not fake pixel data.
198
+ if (image && (typeof image.naturalWidth === 'number' || typeof image.width === 'number')) {
199
+ var w = typeof image.naturalWidth === 'number' ? image.naturalWidth : image.width;
200
+ var h = typeof image.naturalHeight === 'number' ? image.naturalHeight : image.height;
201
+ var dims3 = outputDimensions(w, h, orientation, opts);
202
+ var m3 = buildTransform(w, h, dims3.width, dims3.height, orientation);
203
+ return {
204
+ width: dims3.width,
205
+ height: dims3.height,
206
+ transform: m3, // [a, b, c, d, e, f] — canvas setTransform compatible
207
+ applyTransform: function (ctx) {
208
+ ctx.setTransform(m3[0], m3[1], m3[2], m3[3], m3[4], m3[5]);
209
+ },
210
+ source: image
211
+ };
212
+ }
213
+
214
+ throw new TypeError('nodeTranslate: unsupported image input');
215
+ }
216
+
217
+ module.exports = nodeTranslate;
218
+ module.exports.buildTransform = buildTransform;
219
+ module.exports.outputDimensions = outputDimensions;
220
+ module.exports.transformPixels = transformPixels;
221
+
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@ohos-ports/exif-orientation-image",
3
+ "version": "1.0.1-beta.1",
4
+ "description": "Properly displays an image via canvas based on the exif orientation data. To be used with a file object",
5
+ "main": "index.js",
6
+ "license": "MIT",
7
+ "author": {
8
+ "name": "Nick Poisson",
9
+ "email": "nick@jam3.com",
10
+ "url": "https://github.com/njam3"
11
+ },
12
+ "dependencies": {
13
+ "exif-orientation": "^1.0.0",
14
+ "jpeg-js": "^0.4.4"
15
+ },
16
+ "devDependencies": {
17
+ "tape": "*"
18
+ },
19
+ "scripts": {
20
+ "test": "node test/test.js"
21
+ },
22
+ "keywords": [
23
+ "exif,",
24
+ "orientation,",
25
+ "canvas,",
26
+ "translate,",
27
+ "rotate,",
28
+ "scale,",
29
+ "file"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/ohos-ports/ohos-ports.git",
34
+ "directory": "ports/exif-orientation-image/1.0.1"
35
+ },
36
+ "homepage": "https://github.com/Jam3/exif-orientation-image",
37
+ "bugs": {
38
+ "url": "https://github.com/ohos-ports/ohos-ports/issues"
39
+ },
40
+ "files": [
41
+ "index.js",
42
+ "lib/",
43
+ "LICENSE.md",
44
+ "README.md"
45
+ ]
46
+ }