/// <summary> /// Writes integers in different sizes based on /// digit characters in the <paramref name="format"/> string. /// <para> /// Zero skips a value, while other characters are ignored. /// Maximum size of one integer is 8 bytes. /// </para> /// </summary> public static void WriteFormat( this ImageBinWriter s, ReadOnlySpan <char> format, ReadOnlySpan <long> values) { if (s == null) { throw new ArgumentNullException(nameof(s)); } Span <byte> buffer = stackalloc byte[sizeof(long)]; int valueIndex = 0; for (int i = 0; i < format.Length; i++) { int digit = CharUnicodeInfo.GetDecimalDigitValue(format[i]); if (digit == -1 || digit > buffer.Length) { continue; } if (digit > 0) { long x = values[valueIndex]; for (int j = 0; j < digit; j++) { int shift = 8 * j; buffer[j] = (byte)((x >> shift) & 0xff); } s.Write(buffer.Slice(0, digit)); } valueIndex++; } }
public static void Write <TPixelRowProvider>(ImageBinWriter state, TPixelRowProvider image) where TPixelRowProvider : IPixelRowProvider { if (state == null) { throw new ArgumentNullException(nameof(state)); } image.ThrowIfCancelled(); int width = image.Width; int height = image.Height; if (height <= 0 || width <= 0) { throw new ArgumentException("Invalid image dimensions.", nameof(state)); } CultureInfo cult = CultureInfo.InvariantCulture; byte[] Head1 = Encoding.UTF8.GetBytes(string.Format(cult, "EXPOSURE=1.0\n\n-Y {0} +X {1}\n", height.ToString(cult), width.ToString(cult))); state.Write(Head0.Span); state.Write(Head1); // TODO: pool buffers byte[]? scratch = null; if (width >= 8 && width < 32768) { scratch = new byte[width * 4]; } float[] rowBuffer = new float[width]; for (int row = 0; row < height; row++) { image.ThrowIfCancelled(); image.GetFloatRow(row, rowBuffer); WriteHdrScanline(state, image, rowBuffer, scratch); } }
private static void WriteHdrScanline <TPixelRowProvider>( ImageBinWriter state, TPixelRowProvider image, float[] data, byte[]?buffer) where TPixelRowProvider : IPixelRowProvider { int width = image.Width; int n = image.Components; Span <byte> scanlineHeader = stackalloc byte[4] { 2, 2, (byte)((width & 0xff00) >> 8), (byte)(width & 0x00ff), }; Span <byte> rgbe = stackalloc byte[4]; Span <float> linear = stackalloc float[3]; bool hasColor = n == 4 || n == 3; const int ofsR = 0; int ofsG = hasColor ? 1 : 0; int ofsB = hasColor ? 2 : 0; if (width >= 8 && width < 32768) { Debug.Assert(buffer != null); Span <float> src = data.AsSpan(0, width * n); for (int x = 0; x < src.Length; x += n) { linear[2] = src[x + ofsB]; linear[1] = src[x + ofsG]; linear[0] = src[x + ofsR]; LinearToRgbe(linear, rgbe); buffer[x + width * 3] = rgbe[3]; buffer[x + width * 2] = rgbe[2]; buffer[x + width * 1] = rgbe[1]; buffer[x + width * 0] = rgbe[0]; } state.Write(scanlineHeader); for (int c = 0; c < 4; c++) { int o = width * c; int x = 0; while (x < width) { int r = x; while ((r + 2) < width) { if (buffer[o + r] == buffer[o + r + 2] && buffer[o + r] == buffer[o + r + 1]) { break; } r++; } if (r + 2 >= width) { r = width; } while (x < r) { int len = r - x; if (len > 128) { len = 128; } WriteDumpData(state, buffer.AsSpan(o + x, len)); x += len; } if (r + 2 < width) { while ((r < width) && (buffer[o + r] == buffer[o + x])) { r++; } while (x < r) { int len = r - x; if (len > 127) { len = 127; } WriteRunData(state, len, buffer[o + x]); x += len; } } } } } else { Span <float> src = data.AsSpan(0, width * n); for (int x = 0; x < src.Length; x += n) { linear[2] = src[x + ofsB]; linear[1] = src[x + ofsG]; linear[0] = src[x + ofsR]; LinearToRgbe(linear, rgbe); state.Write(rgbe); } } }
private static void WriteDumpData(ImageBinWriter s, ReadOnlySpan <byte> data) { s.WriteByte((byte)((data.Length) & 0xff)); // lengthbyte s.Write(data); }
public static void WritePixels <TImage>( this ImageBinWriter state, TImage image, bool flipRgb, int verticalDirection, int alphaDirection, int scanlinePad, bool expandMono) where TImage : IPixelRowProvider { if (state == null) { throw new ArgumentNullException(nameof(state)); } if (image == null) { throw new ArgumentNullException(nameof(image)); } if (scanlinePad < 0 || scanlinePad > 4) { throw new ArgumentOutOfRangeException(nameof(scanlinePad)); } if (image.Width <= 0 || image.Height <= 0) { return; } int row; int rowEnd; if (verticalDirection < 0) { rowEnd = -1; row = image.Height - 1; } else { rowEnd = image.Height; row = 0; } int width = image.Width; int comp = image.Components; int stride = width * comp; int scanlineMax = stride + scanlinePad; Span <byte> scanline = scanlineMax <= 4096 ? stackalloc byte[scanlineMax] : new byte[scanlineMax]; for (; row != rowEnd; row += verticalDirection) { image.GetByteRow(row, scanline); int offset = 0; for (int x = 0; x < width; x++) { offset += WritePixel( flipRgb, alphaDirection, expandMono, scanline.Slice(x * comp, comp), scanline.Slice(offset, comp)); } if (offset != stride) { Span <byte> padSlice = scanline.Slice(offset, scanlinePad); padSlice.Clear(); // clear possible garbage from last row offset += scanlinePad; } state.Write(scanline.Slice(0, offset)); } }
public static void WriteCore <TImage>( ImageBinWriter state, TImage image, bool useFloatPixels, int quality, bool allowSubsample, bool forceSubsample) where TImage : IPixelRowProvider { if (state == null) { throw new ArgumentNullException(nameof(state)); } if (image == null) { throw new ArgumentNullException(nameof(image)); } int width = image.Width; int height = image.Height; int comp = image.Components; if (width == 0 || (height == 0)) { throw new ArgumentException("Invalid dimensions.", nameof(image)); } if ((comp < 1) || (comp > 4)) { throw new ArgumentException("Invalid component count.", nameof(image)); } image.ThrowIfCancelled(); Span <float> fdtbl_Y = stackalloc float[64]; Span <float> fdtbl_UV = stackalloc float[64]; Span <byte> YTable = stackalloc byte[64]; Span <byte> UVTable = stackalloc byte[64]; quality = quality != 0 ? quality : 90; quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; quality = quality < 50 ? 5000 / quality : 200 - quality * 2; bool subsample = forceSubsample || (allowSubsample && quality <= 90); ReadOnlySpan <byte> zigZag = ZigZag; for (int i = 0; i < 64; i++) { int yti = (YQT[i] * quality + 50) / 100; YTable[zigZag[i]] = (byte)(yti < 1 ? 1 : yti > 255 ? 255 : yti); int uvti = (UVQT[i] * quality + 50) / 100; UVTable[zigZag[i]] = (byte)(uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); } for (int row = 0, k = 0; row < 8; row++) { for (int col = 0; col < 8; col++, k++) { fdtbl_Y[k] = 1 / (YTable[zigZag[k]] * aasf[row] * aasf[col]); fdtbl_UV[k] = 1 / (UVTable[zigZag[k]] * aasf[row] * aasf[col]); } } { Span <byte> head1 = stackalloc byte[24] { 0xFF, 0xC0, 0, 0x11, 8, (byte)(height >> 8), (byte)((height) & 0xff), (byte)(width >> 8), (byte)((width) & 0xff), 3, 1, (byte)(subsample ? 0x22 : 0x11), 0, 2, 0x11, 1, 3, 0x11, 1, 0xFF, 0xC4, 0x01, 0xA2, 0 }; state.Write(Head0); state.Write(YTable); state.WriteByte(1); state.Write(UVTable); state.Write(head1); state.Write(DefaultDcLuminanceNrcodes[1..DefaultDcChrominanceNrcodes.Length]);