The Wayback Machine - https://web.archive.org/web/20200914065519/http://github.com/microsoft/Win2D/issues/622
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for embedded color profiles in images #622

Open
benstevens48 opened this issue Jun 26, 2018 · 3 comments
Open

Support for embedded color profiles in images #622

benstevens48 opened this issue Jun 26, 2018 · 3 comments
Labels

Comments

@benstevens48
Copy link
Contributor

@benstevens48 benstevens48 commented Jun 26, 2018

Would you consider adding support for reading embedded color profiles in images when creating a CanvasBitmap or CanvasVirtualBitmap? As far as I can see the only way to do this using the UWP C# API and Win2D is to create a SoftwareBitmap or pixel data array from a BitmapDecoder with the ColorManageToSrgb option and then use it to create a CanvasBitmap. However this is quite slow compared to loading from a stream directly, and also it's not supported by CanvasVirtualBitmap (one would have to re-encode to a stream first). It would be good if there was the equivalent of the ColorManageToSrgb option. Alternatively if you could expose a method for reading the color prfile from the image then maybe it would be possible to use a color profile effect to achieve the same result.

@benstevens48
Copy link
Contributor Author

@benstevens48 benstevens48 commented Jul 1, 2018

I managed to write a C++/WinRT runtime component which uses WIC to read the embedded color profile from an image. Using this along with the ColorProfileEffect seemed to work quite well. I couldn't distinguish the result from using a SoftwareBitmap with ColorManageToSrgb set and it was a lot quicker. I would probably make most sense for the UWP BitmapDecoder to have an API for getting the color profile, but in the absence of that, perhaps you could consider adding an API to Win2D? Writing the C++/WinRT runtime component wasn't so easy given the lack of examples of using it on the web (although now I've written it, it's not so bad).

@shawnhar shawnhar added the suggestion label Jul 1, 2018
@shawnhar
Copy link
Member

@shawnhar shawnhar commented Jul 1, 2018

Thanks for the suggestion. This seems like a reasonable thing to support in Win2D, but I'm not sure when we'll have the time to add it.

If you're interested in helping with this, we'd be open to accepting a pull request to add it. The Win2D load API already has a bunch of different overloads, so we should talk about the best way to design this API before anyone puts work into it - the challenge with such things is how to add useful functionality without exploding the method overload matrix by an unmanageable amount :-)

@benstevens48
Copy link
Contributor Author

@benstevens48 benstevens48 commented Jul 8, 2018

Well the simplest thing would be to let people have access to the embedded image color profile, then it can be used as part of a ColorManagementEffect, and people can choose to directly convert to the display profile if desired. Ideally the UWP BitmapDecoder would provide that API. In the absence of that I would suggest a static API like

static ColorManagementProfile[] ColorManagementProfile.LoadFromImageAsync(IRandomAccessStream stream)

(Technically there can be more than one so I put an array as the return type, although maybe it could just load the first one. Also, technically there could be more than one frame, so you could have a parameter to specify that, but it's probably unnecessary since none of the rest of the API supports more than just the first frame.)

Some people might want to convert from the embedded profile on load (which is quite slow for large images but most people won't mind that probably). There are a number of options I can think of in this case (such as converting directly to the display profile, and loading the embedded color profile as above at the same time as loading the image for marginal improved efficiency), but perhaps converting to sRGB would be sufficient. The SoftwareBitmap already offers this possibility, but this is not an option for CanvasVirtualBitmap. I guess it would just require an extra parameter on all the LoadAsync methods for CanvasBitmap and CanvasVirtualBitmap.

Having just learnt C++/WinRT I don't think I have time to learn WRL(?) as well and create a pull request, but in case it is helpful (probably not) here is my C++/WinRT implementation of the first method (or a good approximation) for loading a color profile from an image stream. Note that you would want to change it to return a ColorManagementProfile directly instead of the custom type I am returning.

Windows::Foundation::IAsyncOperation<MyComponent::CustomColorProfile> WicHelper::GetColorProfileAsync(Windows::Storage::Streams::IRandomAccessStream const stream) {
    co_await resume_background();
    com_ptr<IStream> istream{ nullptr };
    check_hresult(CreateStreamOverRandomAccessStream(stream.as<IUnknown>().get(), IID_PPV_ARGS(istream.put())));
    com_ptr<IWICImagingFactory> pFactory{ nullptr };
    check_hresult(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(pFactory.put())));
    com_ptr<IWICBitmapDecoder> pDecoder{ nullptr };
    check_hresult(pFactory->CreateDecoderFromStream(istream.get(), nullptr, WICDecodeMetadataCacheOnDemand, pDecoder.put()));
    com_ptr<IWICBitmapFrameDecode> pFrame{ nullptr };
    check_hresult(pDecoder->GetFrame(0, pFrame.put()));

    UINT actualNumContexts;
    check_hresult(pFrame->GetColorContexts(0, nullptr, &actualNumContexts));
    if (actualNumContexts <= 0) {
        co_return nullptr;
    }
    com_ptr<IWICColorContext> pSourceColorContext{ nullptr };
    check_hresult(pFactory->CreateColorContext(pSourceColorContext.put()));
    IWICColorContext* colorContexts[1] = { pSourceColorContext.get() };
    check_hresult(pFrame->GetColorContexts(1, colorContexts, &actualNumContexts));
    WICColorContextType colorContextType;
    check_hresult(pSourceColorContext->GetType(&colorContextType));
    if (colorContextType == WICColorContextExifColorSpace) {
        UINT exifSpace;
        check_hresult(pSourceColorContext->GetExifColorSpace(&exifSpace));
        MyComponent::CustomExifColorSpace customExifSpace = MyComponent::CustomExifColorSpace::None;
        if (exifSpace == 1) {
            customExifSpace = MyComponent::CustomExifColorSpace::SRgb;
        } else if (exifSpace == 2) {
            customExifSpace = MyComponent::CustomExifColorSpace::AdobeRgb;
        }
        com_array<uint8_t> newBytes;
        com_ptr<CustomColorProfile> result = make_self<CustomColorProfile>(newBytes, customExifSpace); //The type here should be the implemetation type. Requires including CustomColorProfile.h in WicHelper.h.
        co_return result.as<MyComponent::CustomColorProfile>();
    } else if (colorContextType == WICColorContextProfile) {
        UINT numBytes;
        check_hresult(pSourceColorContext->GetProfileBytes(0, nullptr, &numBytes));
        com_array<uint8_t> newBytes = com_array<uint8_t>(numBytes);
        UINT numBytesActual;
        check_hresult(pSourceColorContext->GetProfileBytes(numBytes, newBytes.data(), &numBytesActual));
        com_ptr<CustomColorProfile> result = make_self<CustomColorProfile>(newBytes, MyComponent::CustomExifColorSpace::None); //The type here should be the implemetation type. Requires including CustomColorProfile.h in WicHelper.h.
        co_return result.as<MyComponent::CustomColorProfile>();
    } else {
        co_return nullptr;
    }
}

And for completeness, the custom type.

CustomColorProfile::CustomColorProfile(com_array<uint8_t>& newBytes, MyComponent::CustomExifColorSpace newColorSpace) {
    swap(bytes, newBytes);
    newBytes.clear();
    colorSpace = newColorSpace;
}

com_array<uint8_t> CustomColorProfile::DetachBytes() {
    com_array<uint8_t> arr;
    swap(arr, bytes);
    bytes.clear();
    return arr;
}

MyComponent::CustomExifColorSpace CustomColorProfile::ExifColorSpace() {
    return colorSpace;
}

void CustomColorProfile::Close() {
    bytes.clear();
}

And the conversion to a ColorManagementProfile in C# (including support for an AdobeRGB Exif tag (I think!)).

static ColorManagementProfile CreateColorManagementProfile(MyComponent.CustomColorProfile customColorProfile, CanvasDevice canvasDevice) { //Consumes the custom color profile bytes
    ColorManagementProfile result = null;
    if (customColorProfile.ExifColorSpace == MyComponent.CustomExifColorSpace.None) {
        try {
            var bytes = customColorProfile.DetachBytes();
            if (bytes != null) {
                result = ColorManagementProfile.CreateCustom(bytes);
            }
        } catch (Exception) {
            result = null;
        }
    }else if(customColorProfile.ExifColorSpace == MyComponent.CustomExifColorSpace.AdobeRgb) {
        try {
            if (ColorManagementProfile.IsSupported(ColorManagementProfileType.Simple, canvasDevice)) {
                ColorManagementSimpleProfile simpleProfile = new ColorManagementSimpleProfile() { Gamma = ColorManagementGamma.G22, RedPrimary = new Vector2(0.6400f, 0.3300f), GreenPrimary = new Vector2(0.2100f, 0.7100f), BluePrimary = new Vector2(0.1500f, 0.0600f), WhitePointXZ = new Vector2(0.9505f, 1.0891f) };
                result = ColorManagementProfile.CreateSimple(simpleProfile);
            }
        } catch (Exception) {
            result = null;
        }
    }else if(customColorProfile.ExifColorSpace == MyComponent.CustomExifColorSpace.SRgb) {
        return new ColorManagementProfile(CanvasColorSpace.Srgb);
    }
    return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.
Morty Proxy This is a proxified and sanitized view of the page, visit original site.