IMAGE BASE64 TOOL● Browser-local

IMAGE UTILITY / 02

Base64 to image

Paste a Base64 string and get the picture back — plus a line-by-line verdict on whether the string arrived intact. Decoding happens in your browser. Nothing is uploaded.

01 / Paste the string

Raw Base64 is expected, but a full data: URI, an <img> tag or a CSS url() all work; the wrapper is stripped first. Line breaks, spaces, no-break spaces and the URL-safe alphabet are handled too.

02 / Decoded image

Waiting for a string
Decoded image

Is this string intact?

    Three ways a Base64 image string arrives damaged

    Every broken string comes down to one of three things, and they do not share a fix. Telling them apart is the whole job.

    Cut short

    Bytes at the end never made it. A chat app truncated the message, a terminal buffer ran out, a copy stopped early, a line got dropped when the text was re-wrapped.

    Lost a character

    One character went missing somewhere in the middle — a joined line, a stray edit, a find-and-replace that ate a +. Everything after the gap shifts, so the damage is not confined to where the character went.

    Padding out of place

    Base64 pads the final group with = so the length divides by four. When that = is missing, or sits somewhere it cannot, the decoder refuses the string outright. This is often a symptom rather than the fault itself: it is what losing a character looks like on a string that ended in padding.

    A preview is not evidence

    Every decoder you have used shows you a picture when it can and an error when it cannot. That sounds like a test. It is not. I took one 640 × 360 PNG — 5,879 bytes, 7,840 Base64 characters — and damaged copies of it in a script, then asked Chromium to load each one and compared every rendered row against the intact picture, pixel by pixel.

    What I did to the stringWhat the browser didWhat the bytes sayRows still identical
    Removed the final character (the =)loads, reports 640 × 3605,879 bytes — nothing lost360 of 360
    Removed 10 charactersloads, reports 640 × 3605,872 bytes, IEND chunk gone360 of 360
    Removed 40 charactersloads, reports 640 × 3605,850 bytes358 of 360
    Cut 2% off the endloads5,762 bytes340 of 360
    Cut 10% off the endloads5,292 bytes287 of 360
    Cut 25% off the endloads4,410 bytes280 of 360
    Cut 50% off the endloads2,940 bytes268 of 360
    Cut 75% off the endloads1,470 bytes144 of 360
    Changed one character, at position 3,920loads5,879 bytes, IDAT checksum fails268 of 360
    Deleted one character, at 10% indecoder refuses the string——

    Two rows in that table are the reason this page exists. Removing the last ten characters deletes the PNG end marker, and the picture still comes back identical in all 360 rows. Cutting half the file away still loads and still gets 268 rows right.

    So "it rendered" tells you almost nothing. A decoder that draws a picture has only told you the header was readable.

    The shape of the decay is worth noting too: content disappears far more slowly than bytes do. Losing half the bytes cost me 26% of the rows, not 50%.

    JPEG behaves the same way in outline and worse in one specific spot. Same picture, re-encoded at quality 0.92: 15,084 bytes, 20,112 Base64 characters, and — because the byte count divides by three — no padding at all.

    What I did to the stringWhat the browser didWhat the bytes sayRows still identical
    Removed 1 characterloads15,083 bytes, FF D9 marker gone350 of 360
    Cut 10% off the endloads13,575 bytes286 of 360
    Cut 25% off the endloads11,313 bytes270 of 360
    Cut 50% off the endloads7,542 bytes254 of 360
    Cut 75% off the endloads3,771 bytes158 of 360
    Deleted one character, at 10% inloads, all 360 rows painted15,083 bytes63 of 360
    Deleted one character, at 50% inloads, all 360 rows painted15,083 bytes255 of 360
    Deleted one character, at 90% inloads, all 360 rows painted15,083 bytes287 of 360

    Those last three are the nasty case. Nothing errors. The image box fills completely. And depending on where the character went missing, up to 83% of what you are looking at is wrong.

    The five things this page checks

    1. Length over four

    Base64 turns three bytes into four characters, so an undamaged string always divides by four. A remainder of one is fatal and unconditional: I appended each of the 64 alphabet characters in turn to a string sitting at remainder one, and all 64 came back refused. Remainders of two and three still decode — with something missing.

    2. Where the = sits

    Padding is only ever the last one or two characters, and removing it has to leave a length divisible by four. A 7,839-character string ending in = is refused; the same length without that = decodes cleanly. Three or more = at the end is refused too. This check is what catches a lost character on any string that ended in padding — knock one character out of the middle and the = is left standing somewhere it is not allowed to be.

    3. The signature at the front and the marker at the end

    A PNG opens with 89 50 4E 47 0D 0A 1A 0A and closes with its IEND chunk. A JPEG opens with FF D8 FF and closes with FF D9. Missing end marker means missing bytes.

    For PNG you can be very exact about this. Across twelve PNGs I generated, from 1 × 1 up to 640 × 360, the Base64 always began with iVBORw0KGgo, and it always ended with one of exactly three strings — one per value of the byte length modulo 3:

    Bytes mod 3The string always ends with
    0AAAAAElFTkSuQmCC
    1AABJRU5ErkJggg==
    2AAAASUVORK5CYII=

    If your PNG string ends in anything else, the tail is gone. JPEG has no equivalent: the FF D9 lands on different characters depending on the byte count, so the only honest test is to decode and look at the last two bytes. My JPEG samples all opened with /9j/ followed by 4AAQSkZJ, but those eight characters are the JFIF header and every sample came from the same Chromium encoder, so do not expect them everywhere.

    4. Every PNG chunk's own checksum

    Each PNG chunk carries a CRC32, and browsers do not check them. Change one character in the middle of a PNG string and Chromium loads it happily and draws 268 rows correctly before sliding off — while the IDAT chunk's CRC32 fails on the spot. This page walks the chunks and verifies each one, so that class of damage gets caught instead of being shown to you as a picture.

    5. Declared rows versus painted rows

    The header states a height; this page draws the result and counts how many rows actually came back with pixels. When the two disagree, the image ran out partway. Cut half the bytes off a PNG and Chromium still reports 640 × 360 and still fires its load event, while only 268 rows have anything in them.

    How each one is fixed

    What this page cannot tell you

    How these numbers were produced

    One 640 × 360 PNG, 5,879 bytes, encoded to 7,840 Base64 characters ending in a single =. The same picture re-encoded as JPEG at quality 0.92, 15,084 bytes, 20,112 characters, no padding. Every damaged copy was generated by script rather than by hand, so the damage is exactly what it says it is.

    Each copy was decoded, loaded into an image element, drawn to a canvas, and compared row by row against the intact render. "Rows still identical" counts rows from the top that matched the intact picture exactly; "painted" counts rows holding any non-transparent pixel. Tests ran in Chromium on Windows. It is one image in one browser, so treat the percentages as the shape of the problem rather than as constants.

    Related

    Image to Base64 converter — the other direction: turn a file into Base64, a data URI, an img tag or a CSS background.