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 }