{"record":{"id":"77ebf0a76bd6db1b","repo":"invoke-ai/InvokeAI","slug":"failed-to-read-image-77ebf0","errorCode":null,"errorMessage":"Failed to read image","messagePattern":"Failed to read image","errorType":"http","errorClass":"HTTPException","httpStatus":415,"severity":"error","filePath":"invokeai/app/api/routers/style_presets.py","lineNumber":135,"sourceCode":"        is_public = validated_data.is_public\n\n    except (json.JSONDecodeError, pydantic.ValidationError):\n        raise HTTPException(status_code=400, detail=\"Invalid preset data\")\n\n    record = await asyncio.to_thread(_load_record_or_404, style_preset_id)\n    _assert_preset_write(record, current_user)\n\n    if image is not None:\n        if not image.content_type or not image.content_type.startswith(\"image\"):\n            raise HTTPException(status_code=415, detail=\"Not an image\")\n\n        contents = await image.read()\n        try:\n            pil_image = await asyncio.to_thread(Image.open, io.BytesIO(contents))\n\n        except Exception:\n            ApiDependencies.invoker.services.logger.error(traceback.format_exc())\n            raise HTTPException(status_code=415, detail=\"Failed to read image\")\n\n        try:\n            await asyncio.to_thread(\n                ApiDependencies.invoker.services.style_preset_image_files.save, style_preset_id, pil_image\n            )\n        except ValueError as e:\n            raise HTTPException(status_code=409, detail=str(e))\n    else:\n        try:\n            await asyncio.to_thread(ApiDependencies.invoker.services.style_preset_image_files.delete, style_preset_id)\n        except StylePresetImageFileNotFoundException:\n            pass\n\n    preset_data = PresetData(positive_prompt=positive_prompt, negative_prompt=negative_prompt)\n    changes = StylePresetChanges(name=name, preset_data=preset_data, type=type, is_public=is_public)\n\n    style_preset_image = await asyncio.to_thread(\n        ApiDependencies.invoker.services.style_preset_image_files.get_url, style_preset_id","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/api/routers/style_presets.py#L117-L153","documentation":"update_style_preset reads the uploaded image bytes and decodes them with PIL Image.open inside a thread. Any exception during decode (corrupt data, unsupported format, truncated file) is logged server-side and surfaced as HTTP 415 'Failed to read image'. The Content-Type check passed, but the bytes are not a decodable image.","triggerScenarios":"PATCH /style_presets/i/{id} with an image/* Content-Type but bytes PIL cannot open: truncated download, zero-byte file, wrong extension/actual format mismatch, unsupported format (HEIC on a Pillow build without heif), or text/HTML masquerading with an image Content-Type.","commonSituations":"Pillow without HEIF/WEBP plugin support; aborted uploads yielding partial files; base64 string uploaded instead of decoded binary; CDN error pages served with image/jpeg headers; SVG uploads (PIL cannot open SVG).","solutions":["Open the file locally with PIL Image.open(io.BytesIO(bytes)) before uploading to reproduce and fix the decode failure.","Re-export the image as PNG or JPEG and re-upload; avoid HEIC/SVG which stock Pillow cannot decode.","Check the server log for the traceback the endpoint logs — it names the exact PIL error (e.g. UnidentifiedImageError).","Verify the file is fully transferred (compare byte sizes / checksums) and that you are uploading binary, not base64 text."],"exampleFix":"// before\nconst res = await fetch(url);\nawait res.text(); // HTML error page with image/jpeg header\n\n// after\nconst buf = await res.arrayBuffer();\nif (!isPngOrJpeg(new Uint8Array(buf))) throw new Error(\"not a decodable image\");\nformData.append(\"image\", new Blob([buf], { type: \"image/png\" }));","handlingStrategy":"validation","validationCode":"const MAGIC = {\n  png: [0x89, 0x50, 0x4e, 0x47],\n  jpeg: [0xff, 0xd8, 0xff],\n};\nfunction hasImageMagic(bytes) {\n  return Object.values(MAGIC).some((sig) => sig.every((b, i) => bytes[i] === b));\n}\nconst bytes = new Uint8Array(await image.arrayBuffer());\nif (!hasImageMagic(bytes)) throw new Error(\"file is not a PNG/JPEG payload\");","typeGuard":"function isDecodableImage(bytes: Uint8Array): boolean {\n  const isPng = bytes[0] === 0x89 && bytes[1] === 0x50;\n  const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8;\n  return isPng || isJpeg;\n}","tryCatchPattern":"try {\n  await api.updateStylePreset({ stylePresetId, data, image });\n} catch (e) {\n  if (e.status === 415 && e.detail === \"Failed to read image\") {\n    // server logged the PIL traceback; convert to PNG client-side and retry once\n    const png = await convertToPng(image);\n    await api.updateStylePreset({ stylePresetId, data, image: png });\n  } else throw e;\n}","preventionTips":["Magic-byte-check files before upload; don't trust extensions or Content-Type.","Convert HEIC/SVG/AVIF to PNG/JPEG client-side first.","Verify uploads complete (byte-size or checksum comparison).","Keep server Pillow build codec support in mind when accepting user files."],"tags":["http-415","pillow","image-decoding","file-upload"],"backgroundTag":"image-decode-failed","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}