- 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.
152 lines
6.7 KiB
Plaintext
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;
|
|
}
|