winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

Sixel.cpp (26110B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "Sixel.h"
      5 #include <AppInstallerStrings.h>
      6 #include <winget/UserSettings.h>
      7 #include <vector>
      8 #include <sstream>
      9 
     10 namespace AppInstaller::CLI::VirtualTerminal::Sixel
     11 {
     12     namespace anon
     13     {
     14         wil::com_ptr<IWICImagingFactory> CreateFactory()
     15         {
     16             wil::com_ptr<IWICImagingFactory> result;
     17             THROW_IF_FAILED(CoCreateInstance(
     18                 CLSID_WICImagingFactory,
     19                 NULL,
     20                 CLSCTX_INPROC_SERVER,
     21                 IID_PPV_ARGS(&result)));
     22             return result;
     23         }
     24 
     25         UINT AspectRatioMultiplier(AspectRatio aspectRatio)
     26         {
     27             switch (aspectRatio)
     28             {
     29             case AspectRatio::OneToOne:
     30                 return 1;
     31             case AspectRatio::TwoToOne:
     32                 return 2;
     33             case AspectRatio::ThreeToOne:
     34                 return 3;
     35             case AspectRatio::FiveToOne:
     36                 return 5;
     37             default:
     38                 THROW_HR(E_INVALIDARG);
     39             }
     40         }
     41 
     42         // Forces the given bitmap source to evaluate
     43         wil::com_ptr<IWICBitmap> CacheToBitmap(IWICImagingFactory* factory, IWICBitmapSource* sourceImage)
     44         {
     45             wil::com_ptr<IWICBitmap> result;
     46             THROW_IF_FAILED(factory->CreateBitmapFromSource(sourceImage, WICBitmapCacheOnLoad, &result));
     47             return result;
     48         }
     49 
     50         // Convert [0, 255] => [0, 100]
     51         UINT32 ByteToPercent(BYTE input)
     52         {
     53             return (static_cast<UINT32>(input) * 100 + 127) / 255;
     54         }
     55 
     56         // Contains the state for a rendering pass.
     57         struct RenderState
     58         {
     59             RenderState(
     60                 const Palette& palette,
     61                 const std::vector<ImageView>& views,
     62                 const RenderControls& renderControls) :
     63                 m_palette(palette),
     64                 m_views(views),
     65                 m_renderControls(renderControls)
     66             {
     67                 // Create render buffers
     68                 m_enabledColors.resize(m_palette.Size());
     69                 m_sixelBuffer.resize(m_palette.Size() * m_renderControls.PixelWidth);
     70             }
     71 
     72             enum class State
     73             {
     74                 Initial,
     75                 Pixels,
     76                 Final,
     77                 Terminated,
     78             };
     79 
     80             // Advances the render state machine, returning true if `Current` will return a new sequence and false when it will not.
     81             bool Advance()
     82             {
     83                 std::stringstream stream;
     84 
     85                 switch (m_currentState)
     86                 {
     87                 case State::Initial:
     88                     // Initial device control string
     89                     stream << AICLI_VT_ESCAPE << 'P' << ToIntegral(m_renderControls.AspectRatio) << ";1;q";
     90 
     91                     for (size_t i = 0; i < m_palette.Size(); ++i)
     92                     {
     93                         // 2 is RGB color space, with values from 0 to 100
     94                         stream << '#' << i << ";2;";
     95 
     96                         WICColor currentColor = m_palette[i];
     97                         BYTE red = (currentColor >> 16) & 0xFF;
     98                         BYTE green = (currentColor >> 8) & 0xFF;
     99                         BYTE blue = (currentColor) & 0xFF;
    100 
    101                         stream << ByteToPercent(red) << ';' << ByteToPercent(green) << ';' << ByteToPercent(blue);
    102                     }
    103 
    104                     m_currentState = State::Pixels;
    105                     break;
    106                 case State::Pixels:
    107                 {
    108                     // Disable all colors and set all characters to empty (0x3F)
    109                     memset(m_enabledColors.data(), 0, m_enabledColors.size());
    110                     memset(m_sixelBuffer.data(), 0x3F, m_sixelBuffer.size());
    111 
    112                     // Convert indexed pixel data into per-color sixel lines
    113                     UINT rowsToProcess = std::min(RenderControls::PixelsPerSixel, m_renderControls.PixelHeight - m_currentPixelRow);
    114 
    115                     for (UINT rowOffset = 0; rowOffset < rowsToProcess; ++rowOffset)
    116                     {
    117                         // The least significant bit is the top of the sixel
    118                         char sixelBit = 1 << rowOffset;
    119                         UINT currentRow = m_currentPixelRow + rowOffset;
    120 
    121                         for (UINT i = 0; i < m_renderControls.PixelWidth; ++i)
    122                         {
    123                             const BYTE* pixelPtr = nullptr;
    124                             size_t colorIndex = 0;
    125 
    126                             for (const ImageView& view : m_views)
    127                             {
    128                                 pixelPtr = view.GetPixel(i, currentRow);
    129 
    130                                 if (pixelPtr)
    131                                 {
    132                                     colorIndex = *pixelPtr;
    133 
    134                                     // Stop on the first non-transparent pixel we find
    135                                     if (((m_palette[colorIndex] >> 24) & 0xFF) != 0)
    136                                     {
    137                                         break;
    138                                     }
    139                                 }
    140                             }
    141 
    142                             if (pixelPtr)
    143                             {
    144                                 m_enabledColors[colorIndex] = 1;
    145                                 m_sixelBuffer[(colorIndex * m_renderControls.PixelWidth) + i] += sixelBit;
    146                             }
    147                         }
    148                     }
    149 
    150                     // Output all sixel color lines
    151                     bool firstOfRow = true;
    152 
    153                     for (size_t i = 0; i < m_enabledColors.size(); ++i)
    154                     {
    155                         if (m_enabledColors[i])
    156                         {
    157                             if (m_renderControls.TransparencyEnabled)
    158                             {
    159                                 // Don't output color if transparent
    160                                 WICColor currentColor = m_palette[i];
    161                                 BYTE alpha = (currentColor >> 24) & 0xFF;
    162                                 if (alpha == 0)
    163                                 {
    164                                     continue;
    165                                 }
    166                             }
    167 
    168                             if (firstOfRow)
    169                             {
    170                                 firstOfRow = false;
    171                             }
    172                             else
    173                             {
    174                                 // The carriage return operator resets for another color pass.
    175                                 stream << '$';
    176                             }
    177 
    178                             stream << '#' << i;
    179 
    180                             const char* colorRow = &m_sixelBuffer[i * m_renderControls.PixelWidth];
    181 
    182                             if (m_renderControls.UseRepeatSequence)
    183                             {
    184                                 char currentChar = colorRow[0];
    185                                 UINT repeatCount = 1;
    186 
    187                                 for (UINT j = 1; j <= m_renderControls.PixelWidth; ++j)
    188                                 {
    189                                     // Force processing of a final null character to handle flushing the line
    190                                     const char nextChar = (j == m_renderControls.PixelWidth ? 0 : colorRow[j]);
    191 
    192                                     if (nextChar == currentChar)
    193                                     {
    194                                         ++repeatCount;
    195                                     }
    196                                     else
    197                                     {
    198                                         if (repeatCount > 2)
    199                                         {
    200                                             stream << '!' << repeatCount;
    201                                         }
    202                                         else if (repeatCount == 2)
    203                                         {
    204                                             stream << currentChar;
    205                                         }
    206 
    207                                         stream << currentChar;
    208 
    209                                         currentChar = nextChar;
    210                                         repeatCount = 1;
    211                                     }
    212                                 }
    213                             }
    214                             else
    215                             {
    216                                 stream << std::string_view{ colorRow, m_renderControls.PixelWidth };
    217                             }
    218                         }
    219                     }
    220 
    221                     // The new line operator sets up for the next sixel row
    222                     stream << '-';
    223 
    224                     m_currentPixelRow += rowsToProcess;
    225                     if (m_currentPixelRow >= m_renderControls.PixelHeight)
    226                     {
    227                         m_currentState = State::Final;
    228                     }
    229                 }
    230                     break;
    231                 case State::Final:
    232                     stream << AICLI_VT_ESCAPE << '\\';
    233                     m_currentState = State::Terminated;
    234                     break;
    235                 case State::Terminated:
    236                     m_currentSequence.clear();
    237                     return false;
    238                 }
    239 
    240                 m_currentSequence = std::move(stream).str();
    241                 return true;
    242             }
    243 
    244             Sequence Current() const
    245             {
    246                 return Sequence{ m_currentSequence };
    247             }
    248 
    249         private:
    250             const Palette& m_palette;
    251             const std::vector<ImageView>& m_views;
    252             const RenderControls& m_renderControls;
    253 
    254             State m_currentState = State::Initial;
    255             std::vector<char> m_enabledColors;
    256             std::vector<char> m_sixelBuffer;
    257             UINT m_currentPixelRow = 0;
    258             // TODO-C++20: Replace with a view from the stringstream
    259             std::string m_currentSequence;
    260         };
    261     }
    262 
    263     Palette::Palette(IWICImagingFactory* factory, IWICBitmapSource* bitmapSource, UINT colorCount, bool transparencyEnabled) :
    264         m_factory(factory)
    265     {
    266         THROW_IF_FAILED(m_factory->CreatePalette(&m_paletteObject));
    267 
    268         THROW_IF_FAILED(m_paletteObject->InitializeFromBitmap(bitmapSource, colorCount, transparencyEnabled));
    269 
    270         // Extract the palette for render use
    271         UINT actualColorCount = 0;
    272         THROW_IF_FAILED(m_paletteObject->GetColorCount(&actualColorCount));
    273 
    274         m_palette.resize(actualColorCount);
    275         THROW_IF_FAILED(m_paletteObject->GetColors(actualColorCount, m_palette.data(), &actualColorCount));
    276     }
    277 
    278     Palette::Palette(const Palette& first, const Palette& second)
    279     {
    280         auto firstPalette = first.m_palette;
    281         auto secondPalette = second.m_palette;
    282         std::sort(firstPalette.begin(), firstPalette.end());
    283         std::sort(secondPalette.begin(), secondPalette.end());
    284 
    285         // Construct a union of the two palettes
    286         std::set_union(firstPalette.begin(), firstPalette.end(), secondPalette.begin(), secondPalette.end(), std::back_inserter(m_palette));
    287         THROW_HR_IF(E_INVALIDARG, m_palette.size() > MaximumColorCount);
    288 
    289         m_factory = first.m_factory;
    290         THROW_IF_FAILED(m_factory->CreatePalette(&m_paletteObject));
    291         THROW_IF_FAILED(m_paletteObject->InitializeCustom(m_palette.data(), static_cast<UINT>(m_palette.size())));
    292     }
    293 
    294     IWICPalette* Palette::Get() const
    295     {
    296         return m_paletteObject.get();
    297     }
    298 
    299     size_t Palette::Size() const
    300     {
    301         return m_palette.size();
    302     }
    303 
    304     WICColor& Palette::operator[](size_t index)
    305     {
    306         return m_palette[index];
    307     }
    308 
    309     WICColor Palette::operator[](size_t index) const
    310     {
    311         return m_palette[index];
    312     }
    313 
    314     ImageView::ImageView(UINT width, UINT height, UINT stride, UINT byteCount, BYTE* bytes) :
    315         m_viewWidth(width), m_viewHeight(height), m_viewStride(stride), m_viewByteCount(byteCount), m_viewBytes(bytes)
    316     {}
    317 
    318     ImageView ImageView::Lock(IWICBitmap* imageSource)
    319     {
    320         WICPixelFormatGUID pixelFormat{};
    321         THROW_IF_FAILED(imageSource->GetPixelFormat(&pixelFormat));
    322         THROW_HR_IF(ERROR_INVALID_STATE, GUID_WICPixelFormat8bppIndexed != pixelFormat);
    323 
    324         ImageView result;
    325 
    326         UINT sourceX = 0;
    327         UINT sourceY = 0;
    328         THROW_IF_FAILED(imageSource->GetSize(&sourceX, &sourceY));
    329         THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW,
    330             sourceX > static_cast<UINT>(std::numeric_limits<INT>::max()) || sourceY > static_cast<UINT>(std::numeric_limits<INT>::max()));
    331 
    332         WICRect rect{};
    333         rect.Width = static_cast<INT>(sourceX);
    334         rect.Height = static_cast<INT>(sourceY);
    335 
    336         THROW_IF_FAILED(imageSource->Lock(&rect, WICBitmapLockRead, &result.m_lockedImage));
    337         THROW_IF_FAILED(result.m_lockedImage->GetSize(&result.m_viewWidth, &result.m_viewHeight));
    338         THROW_IF_FAILED(result.m_lockedImage->GetStride(&result.m_viewStride));
    339         THROW_IF_FAILED(result.m_lockedImage->GetDataPointer(&result.m_viewByteCount, &result.m_viewBytes));
    340 
    341         return result;
    342     }
    343 
    344     ImageView ImageView::Copy(IWICBitmapSource* imageSource)
    345     {
    346         WICPixelFormatGUID pixelFormat{};
    347         THROW_IF_FAILED(imageSource->GetPixelFormat(&pixelFormat));
    348         THROW_HR_IF(ERROR_INVALID_STATE, GUID_WICPixelFormat8bppIndexed != pixelFormat);
    349 
    350         ImageView result;
    351 
    352         THROW_IF_FAILED(imageSource->GetSize(&result.m_viewWidth, &result.m_viewHeight));
    353         THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW,
    354             result.m_viewWidth > static_cast<UINT>(std::numeric_limits<INT>::max()) || result.m_viewHeight > static_cast<UINT>(std::numeric_limits<INT>::max()));
    355 
    356         result.m_viewStride = result.m_viewWidth;
    357         result.m_viewByteCount = result.m_viewStride * result.m_viewHeight;
    358         result.m_copiedImage = std::make_unique<BYTE[]>(result.m_viewByteCount);
    359         result.m_viewBytes = result.m_copiedImage.get();
    360 
    361         THROW_IF_FAILED(imageSource->CopyPixels(nullptr, result.m_viewStride, result.m_viewByteCount, result.m_viewBytes));
    362 
    363         return result;
    364     }
    365 
    366     void ImageView::Translate(INT x, INT y, bool tile)
    367     {
    368         m_tile = tile;
    369 
    370         if (m_tile)
    371         {
    372             m_translateX = static_cast<UINT>(m_viewWidth - (x % static_cast<INT>(m_viewWidth)));
    373             m_translateY = static_cast<UINT>(m_viewHeight - (y % static_cast<INT>(m_viewHeight)));
    374         }
    375         else
    376         {
    377             m_translateX = static_cast<UINT>(-x);
    378             m_translateY = static_cast<UINT>(-y);
    379         }
    380     }
    381 
    382     const BYTE* ImageView::GetPixel(UINT x, UINT y) const
    383     {
    384         UINT translatedX = x + m_translateX;
    385         UINT tileCountX = translatedX / m_viewWidth;
    386         UINT viewX = translatedX % m_viewWidth;
    387         if (tileCountX && !m_tile)
    388         {
    389             return nullptr;
    390         }
    391 
    392         UINT translatedY = y + m_translateY;
    393         UINT tileCountY = translatedY / m_viewHeight;
    394         UINT viewY = translatedY % m_viewHeight;
    395         if (tileCountY && !m_tile)
    396         {
    397             return nullptr;
    398         }
    399 
    400         return m_viewBytes + (static_cast<size_t>(viewY) * m_viewStride) + viewX;
    401     }
    402 
    403     UINT ImageView::Width() const
    404     {
    405         return m_viewWidth;
    406     }
    407 
    408     UINT ImageView::Height() const
    409     {
    410         return m_viewHeight;
    411     }
    412 
    413     void RenderControls::RenderSizeInCells(UINT width, UINT height)
    414     {
    415         PixelWidth = width * CellWidthInPixels;
    416 
    417         // We don't want to overdraw the row below, so our height must be the largest multiple of 6 that fits in Y cells.
    418         UINT yInPixels = height * CellHeightInPixels;
    419         PixelHeight = yInPixels - (yInPixels % PixelsPerSixel);
    420     }
    421 
    422     ImageSource::ImageSource(const std::filesystem::path& imageFilePath)
    423     {
    424         m_factory = anon::CreateFactory();
    425 
    426         wil::com_ptr<IWICBitmapDecoder> decoder;
    427         THROW_IF_FAILED(m_factory->CreateDecoderFromFilename(imageFilePath.c_str(), NULL, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder));
    428 
    429         wil::com_ptr<IWICBitmapFrameDecode> decodedFrame;
    430         THROW_IF_FAILED(decoder->GetFrame(0, &decodedFrame));
    431 
    432         m_sourceImage = anon::CacheToBitmap(m_factory.get(), decodedFrame.get());
    433     }
    434 
    435     ImageSource::ImageSource(std::istream& imageStream, Manifest::IconFileTypeEnum imageEncoding)
    436     {
    437         m_factory = anon::CreateFactory();
    438 
    439         wil::com_ptr<IStream> stream;
    440         THROW_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream));
    441 
    442         auto imageBytes = Utility::ReadEntireStreamAsByteArray(imageStream);
    443 
    444         ULONG written = 0;
    445         THROW_IF_FAILED(stream->Write(imageBytes.data(), static_cast<ULONG>(imageBytes.size()), &written));
    446         THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr));
    447 
    448         wil::com_ptr<IWICBitmapDecoder> decoder;
    449         bool initializeDecoder = true;
    450 
    451         switch (imageEncoding)
    452         {
    453         case Manifest::IconFileTypeEnum::Unknown:
    454             THROW_IF_FAILED(m_factory->CreateDecoderFromStream(stream.get(), NULL, WICDecodeMetadataCacheOnDemand, &decoder));
    455             initializeDecoder = false;
    456             break;
    457         case Manifest::IconFileTypeEnum::Jpeg:
    458             THROW_IF_FAILED(m_factory->CreateDecoder(GUID_ContainerFormatJpeg, NULL, &decoder));
    459             break;
    460         case Manifest::IconFileTypeEnum::Png:
    461             THROW_IF_FAILED(m_factory->CreateDecoder(GUID_ContainerFormatPng, NULL, &decoder));
    462             break;
    463         case Manifest::IconFileTypeEnum::Ico:
    464             THROW_IF_FAILED(m_factory->CreateDecoder(GUID_ContainerFormatIco, NULL, &decoder));
    465             break;
    466         default:
    467             THROW_HR(E_UNEXPECTED);
    468         }
    469 
    470         if (initializeDecoder)
    471         {
    472             THROW_IF_FAILED(decoder->Initialize(stream.get(), WICDecodeMetadataCacheOnDemand));
    473         }
    474 
    475         wil::com_ptr<IWICBitmapFrameDecode> decodedFrame;
    476         THROW_IF_FAILED(decoder->GetFrame(0, &decodedFrame));
    477 
    478         m_sourceImage = anon::CacheToBitmap(m_factory.get(), decodedFrame.get());
    479     }
    480 
    481     void ImageSource::Resize(UINT pixelWidth, UINT pixelHeight, AspectRatio targetRenderRatio, bool stretchToFill, InterpolationMode interpolationMode)
    482     {
    483         if ((pixelWidth && pixelHeight) || targetRenderRatio != AspectRatio::OneToOne)
    484         {
    485             UINT targetX = pixelWidth;
    486             UINT targetY = pixelHeight;
    487 
    488             if (!stretchToFill)
    489             {
    490                 // We need to calculate which of the sizes needs to be reduced
    491                 UINT sourceImageX = 0;
    492                 UINT sourceImageY = 0;
    493                 THROW_IF_FAILED(m_sourceImage->GetSize(&sourceImageX, &sourceImageY));
    494 
    495                 double doubleTargetX = targetX;
    496                 double doubleTargetY = targetY;
    497                 double doubleSourceImageX = sourceImageX;
    498                 double doubleSourceImageY = sourceImageY;
    499 
    500                 double scaleFactorX = doubleTargetX / doubleSourceImageX;
    501                 double targetY_scaledForX = sourceImageY * scaleFactorX;
    502                 if (targetY_scaledForX > doubleTargetY)
    503                 {
    504                     // Scaling to make X fill would make Y to large, so we must scale to fill Y
    505                     targetX = static_cast<UINT>(sourceImageX * (doubleTargetY / doubleSourceImageY));
    506                 }
    507                 else
    508                 {
    509                     // Scaling to make X fill kept Y under target
    510                     targetY = static_cast<UINT>(targetY_scaledForX);
    511                 }
    512             }
    513 
    514             // Apply aspect ratio scaling
    515             targetY /= anon::AspectRatioMultiplier(targetRenderRatio);
    516 
    517             wil::com_ptr<IWICBitmapScaler> scaler;
    518             THROW_IF_FAILED(m_factory->CreateBitmapScaler(&scaler));
    519 
    520             THROW_IF_FAILED(scaler->Initialize(m_sourceImage.get(), targetX, targetY, ToEnum<WICBitmapInterpolationMode>(ToIntegral(interpolationMode))));
    521             m_sourceImage = anon::CacheToBitmap(m_factory.get(), scaler.get());
    522         }
    523     }
    524 
    525     void ImageSource::Resize(const RenderControls& controls)
    526     {
    527         Resize(controls.PixelWidth, controls.PixelHeight, controls.AspectRatio, controls.StretchSourceToFill, controls.InterpolationMode);
    528     }
    529 
    530     Palette ImageSource::CreatePalette(UINT colorCount, bool transparencyEnabled) const
    531     {
    532         return { m_factory.get(), m_sourceImage.get(), colorCount, transparencyEnabled };
    533     }
    534 
    535     Palette ImageSource::CreatePalette(const RenderControls& controls) const
    536     {
    537         return CreatePalette(controls.ColorCount, controls.TransparencyEnabled);
    538     }
    539 
    540     void ImageSource::ApplyPalette(const Palette& palette)
    541     {
    542         // Convert to 8bpp indexed
    543         wil::com_ptr<IWICFormatConverter> converter;
    544         THROW_IF_FAILED(m_factory->CreateFormatConverter(&converter));
    545 
    546         // TODO: Determine a better value or enable it to be set
    547         constexpr double s_alphaThreshold = 0.5;
    548 
    549         THROW_IF_FAILED(converter->Initialize(m_sourceImage.get(), GUID_WICPixelFormat8bppIndexed, WICBitmapDitherTypeErrorDiffusion, palette.Get(), s_alphaThreshold, WICBitmapPaletteTypeCustom));
    550         m_sourceImage = anon::CacheToBitmap(m_factory.get(), converter.get());
    551     }
    552 
    553     ImageView ImageSource::Lock() const
    554     {
    555         return ImageView::Lock(m_sourceImage.get());
    556     }
    557 
    558     ImageView ImageSource::Copy() const
    559     {
    560         return ImageView::Copy(m_sourceImage.get());
    561     }
    562 
    563     void Compositor::Palette(Sixel::Palette palette)
    564     {
    565         m_palette = std::move(palette);
    566     }
    567 
    568     void Compositor::AddView(ImageView&& view)
    569     {
    570         m_views.emplace_back(std::move(view));
    571     }
    572 
    573     size_t Compositor::ViewCount() const
    574     {
    575         return m_views.size();
    576     }
    577 
    578     ImageView& Compositor::operator[](size_t index)
    579     {
    580         return m_views[index];
    581     }
    582 
    583     const ImageView& Compositor::operator[](size_t index) const
    584     {
    585         return m_views[index];
    586     }
    587 
    588     RenderControls& Compositor::Controls()
    589     {
    590         return m_renderControls;
    591     }
    592 
    593     const RenderControls& Compositor::Controls() const
    594     {
    595         return m_renderControls;
    596     }
    597 
    598     ConstructedSequence Compositor::Render()
    599     {
    600         anon::RenderState renderState{ m_palette, m_views, m_renderControls };
    601 
    602         std::stringstream result;
    603 
    604         while (renderState.Advance())
    605         {
    606             result << renderState.Current().Get();
    607         }
    608 
    609         return ConstructedSequence{ std::move(result).str() };
    610     }
    611 
    612     void Compositor::RenderTo(Execution::BaseStream& stream)
    613     {
    614         anon::RenderState renderState{ m_palette, m_views, m_renderControls };
    615 
    616         while (renderState.Advance())
    617         {
    618             stream << renderState.Current();
    619         }
    620     }
    621 
    622     void Compositor::RenderTo(Execution::OutputStream& stream)
    623     {
    624         anon::RenderState renderState{ m_palette, m_views, m_renderControls };
    625 
    626         while (renderState.Advance())
    627         {
    628             stream << renderState.Current();
    629         }
    630     }
    631 
    632     Image::Image(const std::filesystem::path& imageFilePath) :
    633         m_imageSource(imageFilePath)
    634     {}
    635 
    636     Image::Image(std::istream& imageStream, Manifest::IconFileTypeEnum imageEncoding) :
    637         m_imageSource(imageStream, imageEncoding)
    638     {}
    639 
    640     Image& Image::AspectRatio(Sixel::AspectRatio aspectRatio)
    641     {
    642         m_renderControls.AspectRatio = aspectRatio;
    643         return *this;
    644     }
    645 
    646     Image& Image::Transparency(bool transparencyEnabled)
    647     {
    648         m_renderControls.TransparencyEnabled = transparencyEnabled;
    649         return *this;
    650     }
    651 
    652     Image& Image::ColorCount(UINT colorCount)
    653     {
    654         THROW_HR_IF(E_INVALIDARG, colorCount > Palette::MaximumColorCount || colorCount < 2);
    655         m_renderControls.ColorCount = colorCount;
    656         return *this;
    657     }
    658 
    659     Image& Image::RenderSizeInPixels(UINT width, UINT height)
    660     {
    661         m_renderControls.PixelWidth = width;
    662         m_renderControls.PixelHeight = height;
    663         return *this;
    664     }
    665 
    666     Image& Image::RenderSizeInCells(UINT width, UINT height)
    667     {
    668         m_renderControls.RenderSizeInCells(width, height);
    669         return *this;
    670     }
    671 
    672     Image& Image::StretchSourceToFill(bool stretchSourceToFill)
    673     {
    674         m_renderControls.StretchSourceToFill = stretchSourceToFill;
    675         return *this;
    676     }
    677 
    678     Image& Image::UseRepeatSequence(bool useRepeatSequence)
    679     {
    680         m_renderControls.UseRepeatSequence = useRepeatSequence;
    681         return *this;
    682     }
    683 
    684     ConstructedSequence Image::Render()
    685     {
    686         return CreateCompositor().second.Render();
    687     }
    688 
    689     void Image::RenderTo(Execution::OutputStream& stream)
    690     {
    691         CreateCompositor().second.RenderTo(stream);
    692     }
    693 
    694     std::pair<ImageSource, Compositor> Image::CreateCompositor()
    695     {
    696         ImageSource localSource{ m_imageSource };
    697         localSource.Resize(m_renderControls);
    698 
    699         Palette palette{ localSource.CreatePalette(m_renderControls) };
    700         localSource.ApplyPalette(palette);
    701 
    702         ImageView view{ localSource.Lock() };
    703 
    704         Compositor compositor;
    705         compositor.Palette(std::move(palette));
    706         compositor.AddView(std::move(view));
    707         compositor.Controls() = m_renderControls;
    708 
    709         return { std::move(localSource), std::move(compositor) };
    710     }
    711 }