Skip to content

Commit 24eda0f

Browse files
committed
Reset browser orientation to defined orientation.
Add tests for combinations of user-defined and EXIF orientation values as well as crop dimensions.
1 parent 5f737b7 commit 24eda0f

File tree

2 files changed

+1748
-315
lines changed

2 files changed

+1748
-315
lines changed

js/load-image-orientation.js

Lines changed: 233 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -103,38 +103,93 @@ Exif orientation values to correctly display the letter F:
103103
})(loadImage)
104104

105105
/**
106-
* Determines if the image requires orientation.
106+
* Determines if the orientation requires a canvas element.
107107
*
108108
* @param {object} [options] Options object
109109
* @param {boolean} [withMetaData] Is meta data required for orientation
110-
* @returns {boolean} Returns true if the image requires orientation
110+
* @returns {boolean} Returns true if orientation requires canvas/meta
111111
*/
112-
function requiresOrientation(options, withMetaData) {
112+
function requiresCanvasOrientation(options, withMetaData) {
113113
var orientation = options && options.orientation
114114
return (
115115
// Exif orientation for browsers without automatic image orientation:
116116
(orientation === true && !loadImage.orientation) ||
117117
// Orientation reset for browsers with automatic image orientation:
118118
(orientation === 1 && loadImage.orientation) ||
119-
// Orientation to defined value, requires meta data for orientation reset:
119+
// Orientation to defined value, requires meta for orientation reset only:
120120
((!withMetaData || loadImage.orientation) &&
121121
orientation > 1 &&
122122
orientation < 9)
123123
)
124124
}
125125

126+
/**
127+
* Determines if the image requires an orientation change.
128+
*
129+
* @param {number} [orientation] Defined orientation value
130+
* @param {number} [autoOrientation] Auto-orientation based on Exif data
131+
* @returns {boolean} Returns true if an orientation change is required
132+
*/
133+
function requiresOrientationChange(orientation, autoOrientation) {
134+
return (
135+
orientation !== autoOrientation &&
136+
((orientation === 1 && autoOrientation > 1 && autoOrientation < 9) ||
137+
(orientation > 1 && orientation < 9))
138+
)
139+
}
140+
141+
/**
142+
* Determines orientation combinations that require a rotation by 180°.
143+
*
144+
* The following is a list of combinations that return true:
145+
*
146+
* 2 (flip) => 5 (rot90,flip), 7 (rot90,flip), 6 (rot90), 8 (rot90)
147+
* 4 (flip) => 5 (rot90,flip), 7 (rot90,flip), 6 (rot90), 8 (rot90)
148+
*
149+
* 5 (rot90,flip) => 2 (flip), 4 (flip), 6 (rot90), 8 (rot90)
150+
* 7 (rot90,flip) => 2 (flip), 4 (flip), 6 (rot90), 8 (rot90)
151+
*
152+
* 6 (rot90) => 2 (flip), 4 (flip), 5 (rot90,flip), 7 (rot90,flip)
153+
* 8 (rot90) => 2 (flip), 4 (flip), 5 (rot90,flip), 7 (rot90,flip)
154+
*
155+
* @param {number} [orientation] Defined orientation value
156+
* @param {number} [autoOrientation] Auto-orientation based on Exif data
157+
* @returns {boolean} Returns true if rotation by 180° is required
158+
*/
159+
function requiresRot180(orientation, autoOrientation) {
160+
if (autoOrientation > 1 && autoOrientation < 9) {
161+
switch (orientation) {
162+
case 2:
163+
case 4:
164+
return autoOrientation > 4
165+
case 5:
166+
case 7:
167+
return autoOrientation % 2 === 0
168+
case 6:
169+
case 8:
170+
return (
171+
autoOrientation === 2 ||
172+
autoOrientation === 4 ||
173+
autoOrientation === 5 ||
174+
autoOrientation === 7
175+
)
176+
}
177+
}
178+
return false
179+
}
180+
126181
// Determines if the target image should be a canvas element:
127182
loadImage.requiresCanvas = function (options) {
128183
return (
129-
requiresOrientation(options) ||
184+
requiresCanvasOrientation(options) ||
130185
originalRequiresCanvas.call(loadImage, options)
131186
)
132187
}
133188

134189
// Determines if meta data should be loaded automatically:
135190
loadImage.requiresMetaData = function (options) {
136191
return (
137-
requiresOrientation(options, true) ||
192+
requiresCanvasOrientation(options, true) ||
138193
originalRequiresMetaData.call(loadImage, options)
139194
)
140195
}
@@ -167,98 +222,222 @@ Exif orientation values to correctly display the letter F:
167222
// based on the given orientation option:
168223
loadImage.getTransformedOptions = function (img, opts, data) {
169224
var options = originalGetTransformedOptions.call(loadImage, img, opts)
225+
var exifOrientation = data.exif && data.exif.get('Orientation')
170226
var orientation = options.orientation
171-
var newOptions
172-
var i
173-
if (orientation === true) {
174-
if (loadImage.orientation) {
175-
// Browser supports automatic image orientation
176-
return options
177-
}
178-
orientation = data && data.exif && data.exif.get('Orientation')
179-
}
180-
if (!(orientation > 1 && orientation < 9)) {
227+
var autoOrientation = loadImage.orientation && exifOrientation
228+
if (orientation === true) orientation = exifOrientation
229+
if (!requiresOrientationChange(orientation, autoOrientation)) {
181230
return options
182231
}
183-
newOptions = {}
184-
for (i in options) {
232+
var top = options.top
233+
var right = options.right
234+
var bottom = options.bottom
235+
var left = options.left
236+
var newOptions = {}
237+
for (var i in options) {
185238
if (Object.prototype.hasOwnProperty.call(options, i)) {
186239
newOptions[i] = options[i]
187240
}
188241
}
189242
newOptions.orientation = orientation
243+
if (
244+
(orientation > 4 && !(autoOrientation > 4)) ||
245+
(orientation < 5 && autoOrientation > 4)
246+
) {
247+
// Image dimensions and target dimensions are switched
248+
newOptions.maxWidth = options.maxHeight
249+
newOptions.maxHeight = options.maxWidth
250+
newOptions.minWidth = options.minHeight
251+
newOptions.minHeight = options.minWidth
252+
newOptions.sourceWidth = options.sourceHeight
253+
newOptions.sourceHeight = options.sourceWidth
254+
}
255+
if (autoOrientation > 1) {
256+
// Browsers which correctly apply source image coordinates to
257+
// auto-oriented images
258+
switch (autoOrientation) {
259+
case 2:
260+
// horizontal flip
261+
right = options.left
262+
left = options.right
263+
break
264+
case 3:
265+
// 180° rotate left
266+
top = options.bottom
267+
right = options.left
268+
bottom = options.top
269+
left = options.right
270+
break
271+
case 4:
272+
// vertical flip
273+
top = options.bottom
274+
bottom = options.top
275+
break
276+
case 5:
277+
// horizontal flip + 90° rotate left
278+
top = options.left
279+
right = options.bottom
280+
bottom = options.right
281+
left = options.top
282+
break
283+
case 6:
284+
// 90° rotate left
285+
top = options.left
286+
right = options.top
287+
bottom = options.right
288+
left = options.bottom
289+
break
290+
case 7:
291+
// vertical flip + 90° rotate left
292+
top = options.right
293+
right = options.top
294+
bottom = options.left
295+
left = options.bottom
296+
break
297+
case 8:
298+
// 90° rotate right
299+
top = options.right
300+
right = options.bottom
301+
bottom = options.left
302+
left = options.top
303+
break
304+
}
305+
// Some orientation combinations require additional rotation by 180°:
306+
if (requiresRot180(orientation, autoOrientation)) {
307+
var tmpTop = top
308+
var tmpRight = right
309+
top = bottom
310+
right = left
311+
bottom = tmpTop
312+
left = tmpRight
313+
}
314+
}
315+
newOptions.top = top
316+
newOptions.right = right
317+
newOptions.bottom = bottom
318+
newOptions.left = left
319+
// Account for defined browser orientation:
190320
switch (orientation) {
191321
case 2:
192322
// horizontal flip
193-
newOptions.left = options.right
194-
newOptions.right = options.left
323+
newOptions.right = left
324+
newOptions.left = right
195325
break
196326
case 3:
197327
// 180° rotate left
198-
newOptions.left = options.right
199-
newOptions.top = options.bottom
200-
newOptions.right = options.left
201-
newOptions.bottom = options.top
328+
newOptions.top = bottom
329+
newOptions.right = left
330+
newOptions.bottom = top
331+
newOptions.left = right
202332
break
203333
case 4:
204334
// vertical flip
205-
newOptions.top = options.bottom
206-
newOptions.bottom = options.top
335+
newOptions.top = bottom
336+
newOptions.bottom = top
207337
break
208338
case 5:
209339
// vertical flip + 90° rotate right
210-
newOptions.left = options.top
211-
newOptions.top = options.left
212-
newOptions.right = options.bottom
213-
newOptions.bottom = options.right
340+
newOptions.top = left
341+
newOptions.right = bottom
342+
newOptions.bottom = right
343+
newOptions.left = top
214344
break
215345
case 6:
216346
// 90° rotate right
217-
newOptions.left = options.top
218-
newOptions.top = options.right
219-
newOptions.right = options.bottom
220-
newOptions.bottom = options.left
347+
newOptions.top = right
348+
newOptions.right = bottom
349+
newOptions.bottom = left
350+
newOptions.left = top
221351
break
222352
case 7:
223353
// horizontal flip + 90° rotate right
224-
newOptions.left = options.bottom
225-
newOptions.top = options.right
226-
newOptions.right = options.top
227-
newOptions.bottom = options.left
354+
newOptions.top = right
355+
newOptions.right = top
356+
newOptions.bottom = left
357+
newOptions.left = bottom
228358
break
229359
case 8:
230360
// 90° rotate left
231-
newOptions.left = options.bottom
232-
newOptions.top = options.left
233-
newOptions.right = options.top
234-
newOptions.bottom = options.right
361+
newOptions.top = left
362+
newOptions.right = top
363+
newOptions.bottom = right
364+
newOptions.left = bottom
235365
break
236366
}
237-
if (newOptions.orientation > 4) {
238-
newOptions.maxWidth = options.maxHeight
239-
newOptions.maxHeight = options.maxWidth
240-
newOptions.minWidth = options.minHeight
241-
newOptions.minHeight = options.minWidth
242-
newOptions.sourceWidth = options.sourceHeight
243-
newOptions.sourceHeight = options.sourceWidth
244-
}
245367
return newOptions
246368
}
247369

248370
// Transform image orientation based on the given EXIF orientation option:
249-
loadImage.transformCoordinates = function (canvas, options) {
250-
originalTransformCoordinates.call(loadImage, canvas, options)
371+
loadImage.transformCoordinates = function (canvas, options, data) {
372+
originalTransformCoordinates.call(loadImage, canvas, options, data)
251373
var orientation = options.orientation
252-
if (!(orientation > 1 && orientation < 9)) {
374+
var autoOrientation =
375+
loadImage.orientation && data.exif && data.exif.get('Orientation')
376+
if (!requiresOrientationChange(orientation, autoOrientation)) {
253377
return
254378
}
255379
var ctx = canvas.getContext('2d')
256380
var width = canvas.width
257381
var height = canvas.height
258-
if (orientation > 4) {
382+
var sourceWidth = width
383+
var sourceHeight = height
384+
if (
385+
(orientation > 4 && !(autoOrientation > 4)) ||
386+
(orientation < 5 && autoOrientation > 4)
387+
) {
388+
// Image dimensions and target dimensions are switched
259389
canvas.width = height
260390
canvas.height = width
261391
}
392+
if (orientation > 4) {
393+
// Destination and source dimensions are switched
394+
sourceWidth = height
395+
sourceHeight = width
396+
}
397+
// Reset automatic browser orientation:
398+
switch (autoOrientation) {
399+
case 2:
400+
// horizontal flip
401+
ctx.translate(sourceWidth, 0)
402+
ctx.scale(-1, 1)
403+
break
404+
case 3:
405+
// 180° rotate left
406+
ctx.translate(sourceWidth, sourceHeight)
407+
ctx.rotate(Math.PI)
408+
break
409+
case 4:
410+
// vertical flip
411+
ctx.translate(0, sourceHeight)
412+
ctx.scale(1, -1)
413+
break
414+
case 5:
415+
// horizontal flip + 90° rotate left
416+
ctx.rotate(-0.5 * Math.PI)
417+
ctx.scale(-1, 1)
418+
break
419+
case 6:
420+
// 90° rotate left
421+
ctx.rotate(-0.5 * Math.PI)
422+
ctx.translate(-sourceWidth, 0)
423+
break
424+
case 7:
425+
// vertical flip + 90° rotate left
426+
ctx.rotate(-0.5 * Math.PI)
427+
ctx.translate(-sourceWidth, sourceHeight)
428+
ctx.scale(1, -1)
429+
break
430+
case 8:
431+
// 90° rotate right
432+
ctx.rotate(0.5 * Math.PI)
433+
ctx.translate(0, -sourceHeight)
434+
break
435+
}
436+
// Some orientation combinations require additional rotation by 180°:
437+
if (requiresRot180(orientation, autoOrientation)) {
438+
ctx.translate(sourceWidth, sourceHeight)
439+
ctx.rotate(Math.PI)
440+
}
262441
switch (orientation) {
263442
case 2:
264443
// horizontal flip

0 commit comments

Comments
 (0)