Sixel.h (10637B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #pragma once 4 #include "ChannelStreams.h" 5 #include "VTSupport.h" 6 #include <winget/ManifestLocalization.h> 7 #include <wincodec.h> 8 #include <wil/com.h> 9 #include <filesystem> 10 11 namespace AppInstaller::CLI::VirtualTerminal::Sixel 12 { 13 // Determines the height to width ratio of the pixels that make up a sixel (a sixel is 6 pixels tall and 1 pixel wide). 14 // Note that each cell is always a height of 20 and a width of 10, regardless of the screen resolution of the terminal. 15 // The 2:1 ratio will then result in each sixel being 12 of the 20 pixels of a cell. 16 enum class AspectRatio 17 { 18 OneToOne = 7, 19 TwoToOne = 0, 20 ThreeToOne = 3, 21 FiveToOne = 2, 22 }; 23 24 // Determines the algorithm used when resizing the image. 25 enum class InterpolationMode 26 { 27 NearestNeighbor = WICBitmapInterpolationModeNearestNeighbor, 28 Linear = WICBitmapInterpolationModeLinear, 29 Cubic = WICBitmapInterpolationModeCubic, 30 Fant = WICBitmapInterpolationModeFant, 31 HighQualityCubic = WICBitmapInterpolationModeHighQualityCubic, 32 }; 33 34 // Contains the palette used by a sixel image. 35 struct Palette 36 { 37 // Limit to 256 both as the defacto maximum supported colors and to enable always using 8bpp indexed pixel format. 38 static constexpr UINT MaximumColorCount = 256; 39 40 // Creates an empty palette. 41 Palette() = default; 42 43 // Create a palette from the given source image, color count, transparency setting. 44 Palette(IWICImagingFactory* factory, IWICBitmapSource* bitmapSource, UINT colorCount, bool transparencyEnabled); 45 46 // Create a palette combining the two palettes. Throws an exception if there are more than MaximumColorCount unique 47 // colors between the two. This can be avoided by intentionally dividing the available colors between the palettes 48 // when creating them. 49 Palette(const Palette& first, const Palette& second); 50 51 // Gets the WIC palette object. 52 IWICPalette* Get() const; 53 54 // Gets the color count for the palette. 55 size_t Size() const; 56 57 // Gets the color at the given index in the palette. 58 WICColor& operator[](size_t index); 59 WICColor operator[](size_t index) const; 60 61 private: 62 wil::com_ptr<IWICImagingFactory> m_factory; 63 wil::com_ptr<IWICPalette> m_paletteObject; 64 std::vector<WICColor> m_palette; 65 }; 66 67 // Allows access to the pixel data of an image source. 68 // Can be configured to translate and/or tile the view. 69 struct ImageView 70 { 71 // Creates a non-owning view using the given data. 72 ImageView(UINT width, UINT height, UINT stride, UINT byteCount, BYTE* bytes); 73 74 // Create a view by locking a bitmap. 75 // This must be used from the same thread as the bitmap. 76 static ImageView Lock(IWICBitmap* imageSource); 77 78 // Create a view by copying the pixels from the image. 79 static ImageView Copy(IWICBitmapSource* imageSource); 80 81 // Translate the view by the given pixel counts. 82 // The pixel at [0, 0] of the original will be at [x, y]. 83 // If tile is true, the view will % coordinates outside of its dimensions back into its own view. 84 // If tile is false, coordinates outside of the view will be null. 85 void Translate(INT x, INT y, bool tile); 86 87 // Gets the pixel of the view at the given coordinate. 88 // Returns null if the coordinate is outside of the view. 89 const BYTE* GetPixel(UINT x, UINT y) const; 90 91 // Get the dimensions of the view. 92 UINT Width() const; 93 UINT Height() const; 94 95 private: 96 ImageView() = default; 97 98 bool m_tile = false; 99 UINT m_translateX = 0; 100 UINT m_translateY = 0; 101 102 wil::com_ptr<IWICBitmapLock> m_lockedImage; 103 std::unique_ptr<BYTE[]> m_copiedImage; 104 105 UINT m_viewWidth = 0; 106 UINT m_viewHeight = 0; 107 UINT m_viewStride = 0; 108 UINT m_viewByteCount = 0; 109 BYTE* m_viewBytes = nullptr; 110 }; 111 112 // The set of values that defines the rendered output. 113 struct RenderControls 114 { 115 // Yes, its right there in the name but the compiler can't read... 116 static constexpr UINT PixelsPerSixel = 6; 117 118 // Each cell is always a height of 20 and a width of 10, regardless of the screen resolution of the terminal. 119 static constexpr UINT CellHeightInPixels = 20; 120 static constexpr UINT CellWidthInPixels = 10; 121 122 Sixel::AspectRatio AspectRatio = AspectRatio::OneToOne; 123 bool TransparencyEnabled = true; 124 bool StretchSourceToFill = false; 125 bool UseRepeatSequence = false; 126 UINT ColorCount = Palette::MaximumColorCount; 127 UINT PixelWidth = 0; 128 UINT PixelHeight = 0; 129 Sixel::InterpolationMode InterpolationMode = InterpolationMode::HighQualityCubic; 130 131 // The resulting sixel image will render to this size in terminal cells, 132 // consuming as much as possible of the given size without going over. 133 void RenderSizeInCells(UINT width, UINT height); 134 }; 135 136 // Contains an image that can be manipulated and rendered to sixels. 137 struct ImageSource 138 { 139 // Create an image source from a file. 140 explicit ImageSource(const std::filesystem::path& imageFilePath); 141 142 // Create an empty image source. 143 ImageSource() = default; 144 145 // Create an image source from a stream. 146 ImageSource(std::istream& imageStream, Manifest::IconFileTypeEnum imageEncoding); 147 148 // Resize the image to the given width and height, factoring in the target aspect ratio for rendering. 149 // If stretchToFill is true, the resulting image will be both the given width and height. 150 // If false, the resulting image will be at most the given width or height while preserving the aspect ratio. 151 void Resize(UINT pixelWidth, UINT pixelHeight, AspectRatio targetRenderRatio, bool stretchToFill = false, InterpolationMode interpolationMode = InterpolationMode::HighQualityCubic); 152 153 // Resizes the image using the given render controls. 154 void Resize(const RenderControls& controls); 155 156 // Creates a palette from the current image. 157 Palette CreatePalette(UINT colorCount, bool transparencyEnabled) const; 158 159 // Creates a palette from the current image. 160 Palette CreatePalette(const RenderControls& controls) const; 161 162 // Converts the image to be 8bpp indexed for the given palette. 163 void ApplyPalette(const Palette& palette); 164 165 // Create a view by locking the image source. 166 // This must be used from the same thread as the image source. 167 ImageView Lock() const; 168 169 // Create a view by copying the pixels from the image source. 170 ImageView Copy() const; 171 172 private: 173 wil::com_ptr<IWICImagingFactory> m_factory; 174 wil::com_ptr<IWICBitmap> m_sourceImage; 175 }; 176 177 // Allows one or more image sources to be rendered to a sixel output. 178 struct Compositor 179 { 180 // Create an empty compositor. 181 Compositor() = default; 182 183 // Set the palette to be used by the compositor. 184 void Palette(Palette palette); 185 186 // Adds a new view to the compositor. Each successive view will be behind all of the others. 187 void AddView(ImageView&& view); 188 189 // Gets the number of views in the compositor. 190 size_t ViewCount() const; 191 192 // Gets the color at the given index in the palette. 193 ImageView& operator[](size_t index); 194 const ImageView& operator[](size_t index) const; 195 196 // Get the render controls for the compositor. 197 RenderControls& Controls(); 198 const RenderControls& Controls() const; 199 200 // Render to sixel format for storage / use multiple times. 201 ConstructedSequence Render(); 202 203 // Renders to sixel format directly to the stream. 204 void RenderTo(Execution::BaseStream& stream); 205 206 // Renders to sixel format directly to the stream. 207 void RenderTo(Execution::OutputStream& stream); 208 209 private: 210 RenderControls m_renderControls; 211 Sixel::Palette m_palette; 212 std::vector<ImageView> m_views; 213 }; 214 215 // A helpful wrapper around the sixel image primitives that makes rendering a single image easier. 216 struct Image 217 { 218 // Create an image from a file. 219 Image(const std::filesystem::path& imageFilePath); 220 221 // Create an image from a stream. 222 Image(std::istream& imageStream, Manifest::IconFileTypeEnum imageEncoding); 223 224 // Set the aspect ratio of the result. 225 Image& AspectRatio(AspectRatio aspectRatio); 226 227 // Determine whether transparency is enabled. 228 // This will affect whether transparent pixels are rendered or not. 229 Image& Transparency(bool transparencyEnabled); 230 231 // If transparency is enabled, one of the colors will be reserved for it. 232 Image& ColorCount(UINT colorCount); 233 234 // The resulting sixel image will render to this size in terminal cell pixels. 235 Image& RenderSizeInPixels(UINT width, UINT height); 236 237 // The resulting sixel image will render to this size in terminal cells, 238 // consuming as much as possible of the given size without going over. 239 Image& RenderSizeInCells(UINT width, UINT height); 240 241 // Only affects the scaling of the image that occurs when render size is set. 242 // When true, the source image will be stretched to fill the target size. 243 // When false, the source image will be scaled while keeping its original aspect ratio. 244 Image& StretchSourceToFill(bool stretchSourceToFill); 245 246 // Compresses the output using repeat sequences. 247 Image& UseRepeatSequence(bool useRepeatSequence); 248 249 // Render to sixel format for storage / use multiple times. 250 ConstructedSequence Render(); 251 252 // Renders to sixel format directly to the output stream. 253 void RenderTo(Execution::OutputStream& stream); 254 255 private: 256 // Creates a compositor for the image using the current render controls. 257 std::pair<ImageSource, Compositor> CreateCompositor(); 258 259 ImageSource m_imageSource; 260 RenderControls m_renderControls; 261 }; 262 }