void ColorSpectrum::CreateBitmapsAndColorMap()

in dev/ColorPicker/ColorSpectrum.cpp [859:1118]


void ColorSpectrum::CreateBitmapsAndColorMap()
{
    auto&& layoutRoot = m_layoutRoot.get();
    auto&& sizingGrid = m_sizingGrid.get();
    auto&& inputTarget = m_inputTarget.get();
    auto&& spectrumRectangle = m_spectrumRectangle.get();
    auto&& spectrumEllipse = m_spectrumEllipse.get();
    auto&& spectrumOverlayRectangle = m_spectrumOverlayRectangle.get();
    auto&& spectrumOverlayEllipse = m_spectrumOverlayEllipse.get();

    if (!m_layoutRoot ||
        !m_sizingGrid ||
        !m_inputTarget ||
        !m_spectrumRectangle ||
        !m_spectrumEllipse ||
        !m_spectrumOverlayRectangle ||
        !m_spectrumOverlayEllipse ||
        SharedHelpers::IsInDesignMode())
    {
        return;
    }

    const double minDimension = min(layoutRoot.ActualWidth(), layoutRoot.ActualHeight());

    if (minDimension == 0)
    {
        return;
    }

    sizingGrid.Width(minDimension);
    sizingGrid.Height(minDimension);

    if (sizingGrid.Clip())
    {
        sizingGrid.Clip().Rect({ 0, 0, static_cast<float>(minDimension), static_cast<float>(minDimension) });
    }

    inputTarget.Width(minDimension);
    inputTarget.Height(minDimension);
    spectrumRectangle.Width(minDimension);
    spectrumRectangle.Height(minDimension);
    spectrumEllipse.Width(minDimension);
    spectrumEllipse.Height(minDimension);
    spectrumOverlayRectangle.Width(minDimension);
    spectrumOverlayRectangle.Height(minDimension);
    spectrumOverlayEllipse.Width(minDimension);
    spectrumOverlayEllipse.Height(minDimension);

    const winrt::float4 hsvColor = HsvColor();
    const int minHue = MinHue();
    int maxHue = MaxHue();
    const int minSaturation = MinSaturation();
    int maxSaturation = MaxSaturation();
    const int minValue = MinValue();
    int maxValue = MaxValue();
    const winrt::ColorSpectrumShape shape = Shape();
    const winrt::ColorSpectrumComponents components = Components();

    // If min >= max, then by convention, min is the only number that a property can have.
    if (minHue >= maxHue)
    {
        maxHue = minHue;
    }

    if (minSaturation >= maxSaturation)
    {
        maxSaturation = minSaturation;
    }

    if (minValue >= maxValue)
    {
        maxValue = minValue;
    }

    const Hsv hsv = { hsv::GetHue(hsvColor), hsv::GetSaturation(hsvColor), hsv::GetValue(hsvColor) };

    // The middle 4 are only needed and used in the case of hue as the third dimension.
    // Saturation and luminosity need only a min and max.
    shared_ptr<vector<::byte>> bgraMinPixelData = make_shared<vector<::byte>>();
    shared_ptr<vector<::byte>> bgraMiddle1PixelData = make_shared<vector<::byte>>();
    shared_ptr<vector<::byte>> bgraMiddle2PixelData = make_shared<vector<::byte>>();
    shared_ptr<vector<::byte>> bgraMiddle3PixelData = make_shared<vector<::byte>>();
    shared_ptr<vector<::byte>> bgraMiddle4PixelData = make_shared<vector<::byte>>();
    shared_ptr<vector<::byte>> bgraMaxPixelData = make_shared<vector<::byte>>();
    shared_ptr<vector<Hsv>> newHsvValues = make_shared<vector<Hsv>>();

    const auto pixelCount = static_cast<size_t>(round(minDimension) * round(minDimension));
    const size_t pixelDataSize = pixelCount * 4;
    bgraMinPixelData->reserve(pixelDataSize);

    // We'll only save pixel data for the middle bitmaps if our third dimension is hue.
    if (components == winrt::ColorSpectrumComponents::ValueSaturation ||
        components == winrt::ColorSpectrumComponents::SaturationValue)
    {
        bgraMiddle1PixelData->reserve(pixelDataSize);
        bgraMiddle2PixelData->reserve(pixelDataSize);
        bgraMiddle3PixelData->reserve(pixelDataSize);
        bgraMiddle4PixelData->reserve(pixelDataSize);
    }

    bgraMaxPixelData->reserve(pixelDataSize);
    newHsvValues->reserve(pixelCount);

    const int minDimensionInt = static_cast<int>(round(minDimension));
    winrt::WorkItemHandler workItemHandler(
        [minDimensionInt, hsv, minHue, maxHue, minSaturation, maxSaturation, minValue, maxValue, shape, components,
        bgraMinPixelData, bgraMiddle1PixelData, bgraMiddle2PixelData, bgraMiddle3PixelData, bgraMiddle4PixelData, bgraMaxPixelData, newHsvValues]
    (winrt::IAsyncAction workItem)
        {
            // As the user perceives it, every time the third dimension not represented in the ColorSpectrum changes,
            // the ColorSpectrum will visually change to accommodate that value.  For example, if the ColorSpectrum handles hue and luminosity,
            // and the saturation externally goes from 1.0 to 0.5, then the ColorSpectrum will visually change to look more washed out
            // to represent that third dimension's new value.
            // Internally, however, we don't want to regenerate the ColorSpectrum bitmap every single time this happens, since that's very expensive.
            // In order to make it so that we don't have to, we implement an optimization where, rather than having only one bitmap,
            // we instead have multiple that we blend together using opacity to create the effect that we want.
            // In the case where the third dimension is saturation or luminosity, we only need two: one bitmap at the minimum value
            // of the third dimension, and one bitmap at the maximum.  Then we set the second's opacity at whatever the value of
            // the third dimension is - e.g., a saturation of 0.5 implies an opacity of 50%.
            // In the case where the third dimension is hue, we need six: one bitmap corresponding to red, yellow, green, cyan, blue, and purple.
            // We'll then blend between whichever colors our hue exists between - e.g., an orange color would use red and yellow with an opacity of 50%.
            // This optimization does incur slightly more startup time initially since we have to generate multiple bitmaps at once instead of only one,
            // but the running time savings after that are *huge* when we can just set an opacity instead of generating a brand new bitmap.
            if (shape == winrt::ColorSpectrumShape::Box)
            {
                for (int x = minDimensionInt - 1; x >= 0; --x)
                {
                    for (int y = minDimensionInt - 1; y >= 0; --y)
                    {
                        if (workItem.Status() == winrt::AsyncStatus::Canceled)
                        {
                            break;
                        }

                        ColorSpectrum::FillPixelForBox(
                            x, y, hsv, minDimensionInt, components, minHue, maxHue, minSaturation, maxSaturation, minValue, maxValue,
                            bgraMinPixelData, bgraMiddle1PixelData, bgraMiddle2PixelData, bgraMiddle3PixelData, bgraMiddle4PixelData, bgraMaxPixelData,
                            newHsvValues);
                    }
                }
            }
            else
            {
                for (int y = 0; y < minDimensionInt; ++y)
                {
                    for (int x = 0; x < minDimensionInt; ++x)
                    {
                        if (workItem.Status() == winrt::AsyncStatus::Canceled)
                        {
                            break;
                        }

                        ColorSpectrum::FillPixelForRing(
                            x, y, minDimensionInt / 2.0, hsv, components, minHue, maxHue, minSaturation, maxSaturation, minValue, maxValue,
                            bgraMinPixelData, bgraMiddle1PixelData, bgraMiddle2PixelData, bgraMiddle3PixelData, bgraMiddle4PixelData, bgraMaxPixelData,
                            newHsvValues);
                    }
                }
            }
        });

    if (m_createImageBitmapAction)
    {
        m_createImageBitmapAction.Cancel();
    }

    m_createImageBitmapAction = winrt::ThreadPool::RunAsync(workItemHandler);
    auto strongThis = get_strong();
    m_createImageBitmapAction.Completed(winrt::AsyncActionCompletedHandler(
        [strongThis, minDimension, components, bgraMinPixelData, bgraMiddle1PixelData, bgraMiddle2PixelData, bgraMiddle3PixelData, bgraMiddle4PixelData, bgraMaxPixelData, newHsvValues]
    (winrt::IAsyncAction asyncInfo, winrt::AsyncStatus asyncStatus)
    {
        if (asyncStatus != winrt::AsyncStatus::Completed)
        {
            return;
        }

        strongThis->m_createImageBitmapAction = nullptr;

        strongThis->m_dispatcherHelper.RunAsync(
            [strongThis, minDimension, bgraMinPixelData, bgraMiddle1PixelData, bgraMiddle2PixelData, bgraMiddle3PixelData, bgraMiddle4PixelData, bgraMaxPixelData, newHsvValues]()
        {
            const int pixelWidth = static_cast<int>(round(minDimension));
            const int pixelHeight = static_cast<int>(round(minDimension));

            const winrt::ColorSpectrumComponents components = strongThis->Components();

            if (SharedHelpers::IsRS2OrHigher())
            {
                winrt::LoadedImageSurface minSurface = CreateSurfaceFromPixelData(pixelWidth, pixelHeight, bgraMinPixelData);
                winrt::LoadedImageSurface maxSurface = CreateSurfaceFromPixelData(pixelWidth, pixelHeight, bgraMaxPixelData);

                switch (components)
                {
                case winrt::ColorSpectrumComponents::HueValue:
                case winrt::ColorSpectrumComponents::ValueHue:
                    strongThis->m_saturationMinimumSurface = minSurface;
                    strongThis->m_saturationMaximumSurface = maxSurface;
                    break;
                case winrt::ColorSpectrumComponents::HueSaturation:
                case winrt::ColorSpectrumComponents::SaturationHue:
                    strongThis->m_valueSurface = maxSurface;
                    break;
                case winrt::ColorSpectrumComponents::ValueSaturation:
                case winrt::ColorSpectrumComponents::SaturationValue:
                    strongThis->m_hueRedSurface = minSurface;
                    strongThis->m_hueYellowSurface = CreateSurfaceFromPixelData(pixelWidth, pixelHeight, bgraMiddle1PixelData);
                    strongThis->m_hueGreenSurface = CreateSurfaceFromPixelData(pixelWidth, pixelHeight, bgraMiddle2PixelData);
                    strongThis->m_hueCyanSurface = CreateSurfaceFromPixelData(pixelWidth, pixelHeight, bgraMiddle3PixelData);
                    strongThis->m_hueBlueSurface = CreateSurfaceFromPixelData(pixelWidth, pixelHeight, bgraMiddle4PixelData);
                    strongThis->m_huePurpleSurface = maxSurface;
                    break;
                }
            }
            else
            {
                winrt::WriteableBitmap minBitmap = CreateBitmapFromPixelData(pixelWidth, pixelHeight, bgraMinPixelData);
                winrt::WriteableBitmap maxBitmap = CreateBitmapFromPixelData(pixelWidth, pixelHeight, bgraMaxPixelData);

                switch (components)
                {
                case winrt::ColorSpectrumComponents::HueValue:
                case winrt::ColorSpectrumComponents::ValueHue:
                    strongThis->m_saturationMinimumBitmap = minBitmap;
                    strongThis->m_saturationMaximumBitmap = maxBitmap;
                    break;
                case winrt::ColorSpectrumComponents::HueSaturation:
                case winrt::ColorSpectrumComponents::SaturationHue:
                    strongThis->m_valueBitmap = maxBitmap;
                    break;
                case winrt::ColorSpectrumComponents::ValueSaturation:
                case winrt::ColorSpectrumComponents::SaturationValue:
                    strongThis->m_hueRedBitmap = minBitmap;
                    strongThis->m_hueYellowBitmap = CreateBitmapFromPixelData(pixelWidth, pixelHeight, bgraMiddle1PixelData);
                    strongThis->m_hueGreenBitmap = CreateBitmapFromPixelData(pixelWidth, pixelHeight, bgraMiddle2PixelData);
                    strongThis->m_hueCyanBitmap = CreateBitmapFromPixelData(pixelWidth, pixelHeight, bgraMiddle3PixelData);
                    strongThis->m_hueBlueBitmap = CreateBitmapFromPixelData(pixelWidth, pixelHeight, bgraMiddle4PixelData);
                    strongThis->m_huePurpleBitmap = maxBitmap;
                    break;
                }
            }

            strongThis->m_shapeFromLastBitmapCreation = strongThis->Shape();
            strongThis->m_componentsFromLastBitmapCreation = strongThis->Components();
            strongThis->m_imageWidthFromLastBitmapCreation = minDimension;
            strongThis->m_imageHeightFromLastBitmapCreation = minDimension;
            strongThis->m_minHueFromLastBitmapCreation = strongThis->MinHue();
            strongThis->m_maxHueFromLastBitmapCreation = strongThis->MaxHue();
            strongThis->m_minSaturationFromLastBitmapCreation = strongThis->MinSaturation();
            strongThis->m_maxSaturationFromLastBitmapCreation = strongThis->MaxSaturation();
            strongThis->m_minValueFromLastBitmapCreation = strongThis->MinValue();
            strongThis->m_maxValueFromLastBitmapCreation = strongThis->MaxValue();

            strongThis->m_hsvValues = *newHsvValues;

            strongThis->UpdateBitmapSources();
            strongThis->UpdateEllipse();
        });
    }));
}