mcap/modules/Image_Decode/wic.jai
Musa Mahmood 811c096dd0 [0.0.8] Import: any folder of screenshots and photos into the library, a folder for each source
- Import window (and -import SOURCE DEST): every image under a folder (PNG, JPEG, HEIC, AVIF, WebP,
  GIF, BMP, TIFF, camera RAW) becomes an .mcap holding the file as it was, a preview turned by its
  EXIF orientation, and metadata.json and import.json Capture Text (date from EXIF, the file name or
  its time; file name as title; camera as app). .snagx files become the same .mcap SnagitConverter
  makes. Duplicates across the whole library are skipped by a hash index (.import-index); videos
  and sidecars are skipped; source files are only read.
- modules/Image_Decode: stb_image, then WIC for everything else; EXIF; orientation; previews.
  tools/image_probe.jai shows what it reads.
- modules/Snagx (and modules/jaison), copied from SnagitConverter.
- The library is a folder of folders: recursive scan, a folder filter, and a folder: search prefix.
- OCR, the viewer and ocr-bench decode any image format, turned the right way up.
- Rename D:\SnagitIndex to D:\McapIndex.
2026-10-09 08:15:12 +01:00

152 lines
6.7 KiB
Plaintext

// Windows Imaging Component: the codecs Windows has, and those the Store extensions add (HEIF and
// AVIF, WebP, Raw Image). Only the few COM methods used are named; the rest of each vtable is padding.
// The first frame of the image in bytes, as non-premultiplied RGBA (default allocator).
wic_decode :: (bytes: [] u8) -> Image, ok: bool, error: string {
image: Image;
if bytes.count > U32_MAX return image, false, "it is too large for WIC";
// Any apartment will do for WIC; a thread already in one keeps it.
initialized := CoInitializeEx(null, COINIT_MULTITHREADED);
defer if initialized == S_OK || initialized == S_FALSE CoUninitialize();
factory: *IWICImagingFactory;
hr := CoCreateInstance(*CLSID_WICImagingFactory, null, CLSCTX_INPROC_SERVER, *IID_IWICImagingFactory, xx *factory);
if failed(hr) return image, false, hr_text("WIC could not start", hr);
defer IUnknown_Release(factory);
stream: *IWICStream;
hr = factory.CreateStream(factory, *stream);
if failed(hr) return image, false, hr_text("WIC could not make a stream", hr);
defer IUnknown_Release(stream);
hr = stream.InitializeFromMemory(stream, bytes.data, cast(u32) bytes.count);
if failed(hr) return image, false, hr_text("WIC could not read the bytes", hr);
decoder: *IWICBitmapDecoder;
hr = factory.CreateDecoderFromStream(factory, stream, null, WICDecodeMetadataCacheOnDemand, *decoder);
if failed(hr) {
if hr == WINCODEC_ERR_COMPONENTNOTFOUND return image, false, "Windows has no codec for it (for HEIC, AVIF, WebP or RAW, install the HEIF, AV1, WebP or Raw Image Extension from the Microsoft Store)";
return image, false, hr_text("WIC cannot read it", hr);
}
defer IUnknown_Release(decoder);
frame: *IWICBitmapFrameDecode;
hr = decoder.GetFrame(decoder, 0, *frame);
if failed(hr) return image, false, hr_text("WIC cannot read its first frame", hr);
defer IUnknown_Release(frame);
converter: *IWICFormatConverter;
hr = factory.CreateFormatConverter(factory, *converter);
if failed(hr) return image, false, hr_text("WIC could not make a converter", hr);
defer IUnknown_Release(converter);
hr = converter.Initialize(converter, frame, *GUID_WICPixelFormat32bppRGBA, WICBitmapDitherTypeNone, null, 0, WICBitmapPaletteTypeCustom);
if failed(hr) return image, false, hr_text("WIC cannot convert it to RGBA", hr);
w, h: u32;
hr = converter.GetSize(converter, *w, *h);
if failed(hr) || !w || !h return image, false, hr_text("WIC gives it no size", hr);
if cast(u64) w * h * 4 > U32_MAX return image, false, tprint("it is % x %, too large to decode in one piece", w, h);
size := cast(s64) w * h * 4;
pixels := cast(*u8) alloc(size,, context.default_allocator);
hr = converter.CopyPixels(converter, null, w * 4, cast(u32) size, pixels);
if failed(hr) {
free(pixels,, context.default_allocator);
return image, false, hr_text("WIC could not decode it", hr);
}
image = .{pixels = pixels, width = cast(s32) w, height = cast(s32) h};
return image, true, "";
}
#scope_file
failed :: (hr: HRESULT) -> bool { return hr < 0; }
hr_text :: (what: string, hr: HRESULT) -> string {
return tprint("% (0x%)", what, formatInt(cast,no_check(u32) hr, base = 16));
}
Ole32 :: #library,system "ole32";
CoInitializeEx :: (pvReserved: *void, dwCoInit: u32) -> HRESULT #foreign Ole32;
CoUninitialize :: () #foreign Ole32;
CoCreateInstance :: (rclsid: *GUID, pUnkOuter: *IUnknown, dwClsContext: u32, riid: *GUID, ppv: **void) -> HRESULT #foreign Ole32;
COINIT_MULTITHREADED :: 0;
CLSCTX_INPROC_SERVER :: 1;
S_OK :HRESULT: 0;
S_FALSE :HRESULT: 1;
WINCODEC_ERR_COMPONENTNOTFOUND :HRESULT: cast,no_check(s32) 0x88982F50;
WICDecodeMetadataCacheOnDemand :: 0;
WICBitmapDitherTypeNone :: 0;
WICBitmapPaletteTypeCustom :: 0;
CLSID_WICImagingFactory := #run uid("CACAF262-9370-4615-A13B-9F5539DA4C0A");
IID_IWICImagingFactory := #run uid("EC5EC8A9-C395-4314-9C77-54D7A935FF70");
GUID_WICPixelFormat32bppRGBA := #run uid("F5C7AD2D-6A8D-43DD-A7A8-A29935261AE9");
IWICImagingFactory :: struct {
#as unknown: IUnknown;
#overlay (unknown.vtable) using vtable: *IWICImagingFactory_VTable;
}
IWICImagingFactory_VTable :: struct {
#as using base: IUnknown_VTable;
CreateDecoderFromFilename: *void;
CreateDecoderFromStream: (this: *IWICImagingFactory, stream: *IWICStream, vendor: *GUID, options: u32, decoder: **IWICBitmapDecoder) -> HRESULT #cpp_method;
CreateDecoderFromFileHandle: *void;
CreateComponentInfo: *void;
CreateDecoder: *void;
CreateEncoder: *void;
CreatePalette: *void;
CreateFormatConverter: (this: *IWICImagingFactory, converter: **IWICFormatConverter) -> HRESULT #cpp_method;
CreateBitmapScaler: *void;
CreateBitmapClipper: *void;
CreateBitmapFlipRotator: *void;
CreateStream: (this: *IWICImagingFactory, stream: **IWICStream) -> HRESULT #cpp_method;
}
IWICStream :: struct {
#as unknown: IUnknown;
#overlay (unknown.vtable) using vtable: *IWICStream_VTable;
}
IWICStream_VTable :: struct {
#as using base: IUnknown_VTable;
Read, Write: *void; // ISequentialStream
Seek, SetSize, CopyTo, Commit, Revert, LockRegion, UnlockRegion, Stat, Clone: *void; // IStream
InitializeFromIStream: *void;
InitializeFromFilename: *void;
InitializeFromMemory: (this: *IWICStream, buffer: *u8, size: u32) -> HRESULT #cpp_method;
}
IWICBitmapDecoder :: struct {
#as unknown: IUnknown;
#overlay (unknown.vtable) using vtable: *IWICBitmapDecoder_VTable;
}
IWICBitmapDecoder_VTable :: struct {
#as using base: IUnknown_VTable;
QueryCapability, Initialize, GetContainerFormat, GetDecoderInfo, CopyPalette, GetMetadataQueryReader, GetPreview, GetColorContexts, GetThumbnail, GetFrameCount: *void;
GetFrame: (this: *IWICBitmapDecoder, index: u32, frame: **IWICBitmapFrameDecode) -> HRESULT #cpp_method;
}
// Only ever passed on, to the converter.
IWICBitmapFrameDecode :: struct {
#as unknown: IUnknown;
}
IWICFormatConverter :: struct {
#as unknown: IUnknown;
#overlay (unknown.vtable) using vtable: *IWICFormatConverter_VTable;
}
IWICFormatConverter_VTable :: struct {
#as using base: IUnknown_VTable;
// IWICBitmapSource
GetSize: (this: *IWICFormatConverter, width: *u32, height: *u32) -> HRESULT #cpp_method;
GetPixelFormat: *void;
GetResolution: *void;
CopyPalette: *void;
CopyPixels: (this: *IWICFormatConverter, rect: *void, stride: u32, size: u32, buffer: *u8) -> HRESULT #cpp_method;
// IWICFormatConverter
Initialize: (this: *IWICFormatConverter, source: *IWICBitmapFrameDecode, format: *GUID, dither: u32, palette: *void, alpha_threshold: float64, palette_type: u32) -> HRESULT #cpp_method;
CanConvert: *void;
}