[{"data":1,"prerenderedAt":41},["ShallowReactive",2],{"$fHnEZQ0IXp6h38J8NEnQ9mtb787Su9tpnlU1YWZ30oNA":3},{"slug":4,"title":5,"description":6,"date":7,"tags":8,"tools":13,"draft":15,"html":16,"toc":17,"minutes":40},"rotating-photos-and-exif-orientation","Why Photos Show Up Sideways, and How EXIF Orientation Works","The eight EXIF orientation values, why a photo that looks fine on your phone turns sideways online, and how ToolOnTap rotates and flips images on a canvas.","2026-10-03",[9,10,11,12],"exif","canvas","images","typescript",[14],"rotate-image",false,"\u003Cp>A photo looks right in your phone’s gallery. You upload it to a form, a forum or a CMS, and it shows up lying on its side. You rotate it on the phone, upload it again, and nothing changes. The photo isn’t broken. It was stored sideways on purpose, with a note saying how to turn it, and the site you uploaded it to ignored the note. When I built \u003Ca href=\"\u002Frotate-image\">Rotate image\u003C\u002Fa>, most of the work went into handling that note correctly.\u003C\u002Fp>\n\u003Ch2 id=\"the-photo-is-stored-sideways-on-purpose\">The photo is stored sideways on purpose\u003C\u002Fh2>\n\u003Cp>A phone camera’s sensor has a fixed “up”. When you hold the phone in portrait, the sensor is lying on its side, and the pixels come out lying on their side too. Many cameras don’t turn the pixels before saving. They save them as the sensor saw them and add an EXIF tag, Orientation (\u003Ccode>0x0112\u003C\u002Fcode>), that tells viewers how to turn the picture before showing it.\u003C\u002Fp>\n\u003Cp>Phone galleries and current browsers read the tag, so the photo looks right there. Anything that reads only the pixels shows it the way the sensor saw it.\u003C\u002Fp>\n\u003Ch2 id=\"the-eight-orientation-values\">The eight orientation values\u003C\u002Fh2>\n\u003Cp>The tag holds a number from 1 to 8. Each describes one of the eight ways a rectangle can be turned and mirrored. Written as “mirror first, then turn clockwise”, they are:\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\u003Cthead>\n\u003Ctr>\n\u003Cth>Value\u003C\u002Fth>\n\u003Cth>What the viewer must do\u003C\u002Fth>\n\u003Cth>Mirror\u003C\u002Fth>\n\u003Cth>Turn\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>1\u003C\u002Ftd>\n\u003Ctd>Nothing, already upright\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>0°\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>2\u003C\u002Ftd>\n\u003Ctd>Mirror left to right\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>0°\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>3\u003C\u002Ftd>\n\u003Ctd>Turn 180°\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>180°\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>4\u003C\u002Ftd>\n\u003Ctd>Mirror top to bottom\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>180°\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>5\u003C\u002Ftd>\n\u003Ctd>Mirror, then turn 270° clockwise\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>270°\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>6\u003C\u002Ftd>\n\u003Ctd>Turn 90° clockwise\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>90°\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>7\u003C\u002Ftd>\n\u003Ctd>Mirror, then turn 90° clockwise\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>90°\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>8\u003C\u002Ftd>\n\u003Ctd>Turn 270° clockwise (90° left)\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>270°\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\u003Cp>Photos from phone cameras normally use 1, 3, 6 or 8. The mirrored values are rare, but a correct reader still has to handle them.\u003C\u002Fp>\n\u003Ch2 id=\"why-some-apps-get-it-wrong\">Why some apps get it wrong\u003C\u002Fh2>\n\u003Cp>There are two common ways a photo ends up sideways:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>The app ignores the tag.\u003C\u002Fstrong> Older software, some image libraries and some upload pipelines read the raw pixels and never look at EXIF.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>The tag is removed without turning the pixels.\u003C\u002Fstrong> Many sites strip metadata from uploads, which is good for privacy. If they strip it before applying the orientation, the instruction is lost and the sideways pixels are all that’s left.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Rotating the photo on your phone often doesn’t help, because the gallery may change only the tag again. The fix that survives every app is to turn the pixels themselves and save a file that needs no tag at all. That’s what the tool does.\u003C\u002Fp>\n\u003Ch2 id=\"eight-states-and-one-canonical-form\">Eight states and one canonical form\u003C\u002Fh2>\n\u003Cp>The buttons are “Rotate left”, “Rotate right”, “Flip horizontally” and “Flip vertically”, and you can press them in any order. Storing the list of presses would mean replaying it every time. Instead, \u003Ccode>utils\u002Forient.ts\u003C\u002Fcode> keeps one small state:\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">export\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> interface\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> Orientation\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#b35309;--shiki-dark:#FFAB70\">rotate\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> |\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 90\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> |\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 180\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> |\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 270\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">; \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#b35309;--shiki-dark:#FFAB70\">flip\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> boolean\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> }\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Four turns times mirrored or not gives exactly eight states, the same eight as the EXIF table above. And it uses the same order: mirror left to right first, then turn clockwise. Any sequence of button presses collapses into one of them.\u003C\u002Fp>\n\u003Cp>Rotating is just adding or taking away 90°. Flipping is the interesting part, because the buttons act on the image as you see it right now, which may already be turned:\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#57606a;--shiki-dark:#8b949e\">\u002F\u002F Mirroring what's on screen: H·R(r) = R(−r)·H, and a vertical mirror is a horizontal one turned 180°\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">export\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> const\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> flipHorizontal\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#b35309;--shiki-dark:#FFAB70\">o\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> Orientation\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> Orientation\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> =>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> ({ rotate: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">turn\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">o.rotate), flip: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">o.flip })\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">export\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> const\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> flipVertical\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#b35309;--shiki-dark:#FFAB70\">o\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> Orientation\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> Orientation\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> =>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> ({ rotate: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">turn\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">180\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> -\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> o.rotate), flip: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">o.flip })\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The comment is the whole idea. Mirroring an image that was turned by r gives the same result as mirroring first and turning by −r. So a horizontal flip negates the turn and toggles the mirror. A vertical flip is a horizontal flip followed by a half turn, so it becomes 180° − r.\u003C\u002Fp>\n\u003Cp>It’s the kind of code that looks right and is easy to get wrong, so the tests don’t trust the formulas. They draw a tiny 3 × 2 grid of letters with a reference renderer and check, from several starting states, that each button changes the rendered grid exactly as pressing it on screen would. They also check that four right turns bring you back to the original, and that flipping horizontally and then vertically equals a plain 180° turn.\u003C\u002Fp>\n\u003Cp>The same state drives the status line under the buttons. It describes the result in the order people press buttons, such as “Rotated 90° right, then flipped horizontally”, and “Reset” goes back to “Original orientation”.\u003C\u002Fp>\n\u003Ch2 id=\"drawing-it-on-a-canvas\">Drawing it on a canvas\u003C\u002Fh2>\n\u003Cp>The pixels are turned with a canvas. This is the core of \u003Ccode>composables\u002Fimage\u002FuseRotate.ts\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#57606a;--shiki-dark:#8b949e\">\u002F\u002F Canvas transforms apply last-to-first: mirror, then rotate, then move to the centre\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">ctx.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">translate\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(out.width \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 2\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, out.height \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 2\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">ctx.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">rotate\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">((o.rotate \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">*\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> Math.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">PI\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 180\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">if\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (o.flip) ctx.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">scale\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">ctx.imageSmoothingQuality \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> 'high'\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">ctx.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">drawImage\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(bmp, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">size.width \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 2\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">size.height \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 2\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, size.width, size.height)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Canvas transforms are applied to the drawing in the reverse order of the calls. So the image is mirrored first, then turned, then moved so its center sits in the middle of the canvas, which matches the canonical order exactly. For a quarter turn, the canvas is created with width and height swapped, so a 4000 × 3000 photo becomes 3000 × 4000.\u003C\u002Fp>\n\u003Cp>Before this runs, the image is decoded with \u003Ccode>createImageBitmap\u003C\u002Fcode>, and here the original EXIF tag matters again. The code relies on the browser applying the photo’s own orientation while decoding, as current browsers do. That way the preview matches what you see in your gallery, and your turn comes on top of it. The file the canvas produces has no EXIF at all, so there’s no tag left for another app to misread, and none of the original’s hidden data such as the GPS location.\u003C\u002Fp>\n\u003Cp>Two limits come from the canvas itself:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Size.\u003C\u002Fstrong> Browsers refuse very large canvases, and iOS draws nothing instead of failing. The tool keeps the output within 16,777,216 pixels (the area of 4096 × 4096), scaling bigger photos down and saying so next to the file.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Speed.\u003C\u002Fstrong> Every image is redrawn each time you press a button, 120 ms after the last press, so quick clicks don’t start a redraw each.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"quality-formats-and-the-safari-exception\">Quality, formats and the Safari exception\u003C\u002Fh2>\n\u003Cp>Each image keeps its format where the browser can write it:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>PNG\u003C\u002Fstrong> stays PNG and is lossless, so turning it costs nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>JPG and WebP\u003C\u002Fstrong> are saved again at quality 0.92. That isn’t visible in normal use. Each redraw starts again from your original file, so the download has been through one encoding, however many times you pressed the buttons.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Other formats\u003C\u002Fstrong>, such as GIF, are saved as JPG.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>A lossless JPEG rotation does exist. Tools like \u003Ccode>jpegtran\u003C\u002Fcode> rearrange the compressed blocks without decoding them. Browsers don’t offer that, so a canvas tool has to encode again. At 0.92, done once, I think that’s the right trade.\u003C\u002Fp>\n\u003Cp>Safari, and every browser on iPhone and iPad, can’t encode WebP. When the canvas is asked for WebP there, it quietly returns a PNG instead. The code checks the type of what it got back, names the file \u003Ccode>.png\u003C\u002Fcode> to match, and tells you why:\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>Your browser can’t save WebP, so it was saved as a PNG.\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>So you never download a file whose extension lies about what’s inside.\u003C\u002Fp>\n\u003Ch2 id=\"try-it\">Try it\u003C\u002Fh2>\n\u003Cp>\u003Ca href=\"\u002Frotate-image\">Rotate image\u003C\u002Fa> turns and mirrors one image or a whole batch, with the same change applied to every file, and downloads them one by one or as a ZIP. Everything happens in your browser. For more ways to fix up photos, see the \u003Ca href=\"\u002Fimage-tools\">image tools\u003C\u002Fa>.\u003C\u002Fp>\n",[18,22,25,28,31,34,37],{"id":19,"text":20,"level":21},"the-photo-is-stored-sideways-on-purpose","The photo is stored sideways on purpose",2,{"id":23,"text":24,"level":21},"the-eight-orientation-values","The eight orientation values",{"id":26,"text":27,"level":21},"why-some-apps-get-it-wrong","Why some apps get it wrong",{"id":29,"text":30,"level":21},"eight-states-and-one-canonical-form","Eight states and one canonical form",{"id":32,"text":33,"level":21},"drawing-it-on-a-canvas","Drawing it on a canvas",{"id":35,"text":36,"level":21},"quality-formats-and-the-safari-exception","Quality, formats and the Safari exception",{"id":38,"text":39,"level":21},"try-it","Try it",6,1792325805008]