Back to Blog

The PNG Hole Went Black

September 18, 2026
Share:
The PNG Hole Went Black

I saved a PNG with a hole in it and asked a converter for a JPEG. The hole came back black. I had not picked black. The page had not asked. JPEG has no see-through pixels, so the encoder filled the empty bits with whatever default it had, and that default was black.

I built a tab that asks first. You pick the fill colour. You see the hole on a checkerboard before you flatten it. You download one file, or a ZIP if you dropped a folder. The photos stay in this browser after the page has loaded.

The new page is Image Format Converter. PNG, JPG, WebP, AVIF, GIF, or iPhone HEIC in. JPEG, PNG, WebP, or AVIF out, if this browser can write that type. Mixpanel still loads with Softery.io, so I will not claim the whole site never talks to a server.


The hole went black

A PNG with a cutout is a shop listing, a logo, a sticker. JPEG is what a form wants. Those two facts sit in the same afternoon a lot.

The usual path is: upload, wait, download, open, swear. The see-through pixels became a black rectangle. Or white, if you got lucky and the encoder used white. You only find out after the file is back.

This tab draws the original on a checkerboard. Holes show as the board. Convert to JPEG and the holes take the colour in the picker. Default is #ffffff. Change it. Convert again. The old result is dropped. Nothing is saved until you hit Download.

The sample is a 640 by 400 PNG drawn in this tab. The left half is a blue block. The right half is empty, with the word HOLE next to it. Convert that to JPEG and watch the empty half fill. Replace it with your own file when you have seen that once.

JPEG has no see-through pixels. Holes get this colour instead of going black by default.

That line sits under the picker. If you wanted the hole to stay a hole, pick PNG or WebP.


Magic bytes, not the suffix

A file named shot.jpg can still be a PNG. I have seen that from phone shares, from "Save image as", from a rename in Finder. If I trusted the suffix, I would feed PNG bytes to a JPEG decoder and fail, or worse, succeed on garbage.

This tab sniffs the first bytes. PNG starts 89 50 4E 47. JPEG starts FF D8 FF. WebP is RIFF then WEBP. GIF starts GIF. AVIF and HEIC are ftyp boxes with a brand. A PDF that lands in the drop is refused. The list under the drop says when the filename does not match the bytes. The download uses the real format.

I did not invent a second HEIC path. iPhone photos go through the same decoder as HEIC to JPG. Safari can read HEIC in createImageBitmap. Chrome and Firefox load a decoder module the first time a HEIC file is in the drop. You will see that load. The module does not send the photo anywhere.

GIF is an input. Images to PDF refuses GIF. This page accepts it and writes the first frame. That is a loss. The list says "first frame only" before you convert.


What this browser can write

Outputs are JPEG, PNG, WebP, and AVIF. I do not ship a silent server encoder when the tab cannot write a type. I ask canvas.toBlob for each MIME type on a 2 by 2 canvas when the page loads. If AVIF comes back empty, that radio is disabled. The line under the radios says so.

Chrome and Safari can decode AVIF today. Encoding AVIF in a canvas is a different question. I will not promise AVIF out in a browser I have not probed. If your browser writes it, the radio works. If it does not, pick JPEG, PNG, or WebP. I watched the probe return false for AVIF in the test I wrote this sitting. Live Chrome may differ. The page tells you what it got, not what a blog post hoped.

Quality is a slider from 1 to 100. Default is 92. It applies to JPEG, WebP, and AVIF. PNG is lossless. The slider hides for PNG and a line says quality does not apply. I am not going to pretend a PNG quality knob does anything. 92 is the same default I used on HEIC to JPG. It is not a claim that 92 is "visually lossless." Open the download. If it looks mushy, raise the slider and convert again.

InputOutput if this browser can encode it
JPEG, PNG, WebP, AVIF, GIF, HEICJPEG, PNG, WebP, AVIF

Same pixels. Different container. A JPEG rewritten as PNG often grows. A PNG rewritten as JPEG often shrinks and loses the hole unless you pick a fill. The page warns before you convert when PNG is the output from a lossy original. After encode, it compares byte counts. If the download is larger, it says so. I will not promise smaller files.


JPEG has no holes

PNG color type 6 is RGBA. A tRNS chunk is keyed transparency. WebP VP8X has an alpha bit. JPEG has none of that.

When the output is JPEG, the canvas is filled with the background colour, then the photo is drawn on top. Holes become that colour. When the output is PNG or WebP, the canvas is cleared and the see-through layer stays.

AVIF can keep alpha if the encoder does. If this browser cannot write AVIF, you never get to that question.

The original preview and the result preview both sit on the same checkerboard. That is how you tell whether the hole is still a hole. Side by side. Same scale as the file, not a thumbnail that hides the fill.


Frames stop at one

An animated GIF has more than one image descriptor. APNG has an acTL chunk. Animated WebP sets the ANIM bit on VP8X. createImageBitmap gives me the first frame. I write that frame. The loop is gone.

I could have refused GIF. I did that on Images to PDF, because a PDF page is not a cartoon. Here the job is "make this a JPEG I can attach." The first frame is often the frame people wanted. The copy still says the rest of the frames are not in the download. If you need the loop, keep the GIF.

I do not reconstruct animation in AVIF or animated WebP out. That is a different product.


Colour profiles do not come along

A PNG can carry an iCCP chunk. A JPEG can carry ICC_PROFILE in APP2. A WebP can carry ICCP. Canvas encode uses this tab's pixels. The profile does not come along.

If the original was Adobe RGB and this tab draws it in sRGB, the download is what you saw, not a bit-identical copy of the profiled file. I name that instead of calling the result lossless in every sense. PNG out is lossless as pixels in this canvas. It is not a round trip of metadata.

EXIF on HEIC is a HEIC to JPG job. This converter does not write EXIF back onto JPEG. If you need capture dates kept, use that page.


A ZIP for a folder

One file downloads as shot.jpg, or .png, or .webp, from the original stem. Two or more become converted-N-jpg.zip, or webp, or png, matching the output. I reuse the ZIP builder already in the HEIC tool. JSZip, in this tab.

A run takes 100 files. Each file stops at 40 MB, counted as 40 * 1024 * 1024 bytes, same cap as Resize Image and Compress Image to Exact KB. Safari stops a canvas at 16,777,216 pixels. A huge phone photo is scaled to that cap first. Progress is N of M. Stop cancels between files. Cancel is an AbortSignal. If you click Stop mid-file, that file may still finish the canvas write. The next one does not start.

I yield to the event loop after each file so the progress line can paint. I did not put this path in a worker. Compress Image and HEIC to JPG run the same way: sequential, on the main thread, with a Stop button. A hundred large photos can still hitch the tab. That hitch is this browser drawing. It is not an upload.


Caps I counted

| Cap | Value | Where | | --- | --- | | Files in one run | 100 | acceptLimits.maxFiles | | Bytes per file | 40 × 1024 × 1024 | acceptLimits.maxBytes | | Pixels | 16,777,216 | Safari canvas, same as Resize Image | | Quality default | 92 | defaultSettings | | Sample | 640 × 400 PNG | makeSampleFile | | Settings key | softery.image-format-converter.settings | localStorage |

Those numbers are from this sitting. I did not round 40 MB into 40,000,000 SI here because the code uses 1024. The fail copy still says "over 40 MB", same sentence as the compressor.

A PDF in the drop is skipped. A 101st photo is cut. The alert names the reason. The photos that fit stay.


HEIC uses the decoder we already have

I did not add a fourth HEIC library. encodePhoto calls createImageBitmap first. If that throws, it calls decodeHeic to PNG, then draws that. decodeHeic is Safari native, then heic2any, then heic-to. That order is already tested on HEIC to JPG. Chrome hits the module load. You will see it. Mixpanel does not get the photo.

If you only wanted iPhone photos as JPEG with EXIF kept, this page is the wrong first click. HEIC to JPG keeps names and capture dates. This page is mixed formats, a colour for JPEG holes, and a ZIP.


Settings stay, photos do not

Format, quality, and the fill colour sit in this browser under softery.image-format-converter.settings. Clear settings wipes that key and puts JPEG, 92, and #ffffff back. The photos are never in localStorage. They are object URLs for this visit.

Copy settings link puts fmt, q, and bg on the URL. You see that URL before you copy it. A passport scan does not travel in a query string. Share is for "JPEG at 80 with a white fill", not for the file. If you pasted that link to yourself on another machine, you still have to drop the photos again. The knobs travel. The pixels do not.

Mixpanel slug is image-format-converter. Events carry file count, a size bucket, the format name, whether a download grew. They do not carry the photo. They do not carry the bytes. Google Analytics loads with the rest of Softery.io. Same rule as every other tool.

This is the forty-second free tool. I counted FREE_TOOLS in this sitting.


Nearby tools that are a different job

After a result, two cards sit under the download. HEIC to JPG if the only problem is iPhone photos. Compress Image to Exact KB if the form printed a cap. Resize Image if you need exact pixels. Those are live routes. I checked them before I linked them.

I did not stamp a landing page per pair. png-to-jpg and webp-to-png would be forty extra URLs for one canvas. One page, one probe, one ZIP.


Convert it, then open it

Drop a file or use the sample. Pick JPEG if a form wants JPEG. Pick the fill colour if there is a hole. Hit Convert. Read the lines under the result. If the file grew, that line is not a bug. PNG from a small JPEG is often larger. Download. Open the file on your machine. Look at the hole. Look at the first frame. If it is wrong, change a knob and convert again.

The files never left this tab to do that job.


Last updated: September 18, 2026 | Reading time: 10 minutes

Written by Softery.io, after a PNG hole came back black and nobody had asked about the colour.