initial commit
Pulsar .NET 9.0 Windows Release / build (push) Waiting to run
Mirror to Codeberg and Gitea / mirror (push) Waiting to run

This commit is contained in:
i2p
2026-08-27 10:57:58 -06:00
commit 773d05f8f1
1038 changed files with 109261 additions and 0 deletions
BIN
View File
Binary file not shown.
@@ -0,0 +1,451 @@
using Pulsar.Common.Video.Compression;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.CompilerServices;
namespace Pulsar.Common.Video.Codecs
{
public class UnsafeStreamCodec : IDisposable
{
public int Monitor { get; private set; }
public Resolution Resolution { get; private set; }
public Size CheckBlock { get; private set; }
public int ImageQuality
{
get { return _imageQuality; }
private set
{
if (_imageQuality == value) return;
lock (_imageProcessLock)
{
_imageQuality = value;
if (_jpgCompression != null)
{
_jpgCompression.Dispose();
}
_jpgCompression = new JpgCompression(_imageQuality);
}
}
}
private int _imageQuality;
private byte[] _encodeBuffer;
private Bitmap _decodedBitmap;
private PixelFormat _encodedFormat;
private int _encodedWidth;
private int _encodedHeight;
private readonly object _imageProcessLock = new object();
private JpgCompression _jpgCompression;
private bool _disposed;
private readonly List<Rectangle> _workingBlocks = new List<Rectangle>();
private readonly List<Rectangle> _finalUpdates = new List<Rectangle>();
private readonly byte[] _metadataBuffer = new byte[20]; // 5 * sizeof(int)
private readonly byte[] _lengthBuffer = new byte[4];
/// <summary>
/// Initialize a new instance of UnsafeStreamCodec class.
/// </summary>
/// <param name="imageQuality">The quality to use between 0-100.</param>
/// <param name="monitor">The monitor used for the images.</param>
/// <param name="resolution">The resolution of the monitor.</param>
public UnsafeStreamCodec(int imageQuality, int monitor, Resolution resolution)
{
if (imageQuality < 0 || imageQuality > 100)
throw new ArgumentOutOfRangeException("imageQuality", "Image quality must be between 0 and 100");
this.ImageQuality = imageQuality;
this.Monitor = monitor;
this.Resolution = resolution;
this.CheckBlock = new Size(50, 1);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
if (_decodedBitmap != null)
{
_decodedBitmap.Dispose();
}
if (_jpgCompression != null)
{
_jpgCompression.Dispose();
}
}
_disposed = true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetPixelSize(PixelFormat format)
{
switch (format)
{
case PixelFormat.Format24bppRgb:
case PixelFormat.Format32bppRgb:
return 3;
case PixelFormat.Format32bppArgb:
case PixelFormat.Format32bppPArgb:
return 4;
default:
throw new NotSupportedException(string.Format("Pixel format {0} is not supported", format));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe byte* GetScan0Pointer(IntPtr scan0)
{
if (IntPtr.Size == 8)
{
return (byte*)scan0.ToInt64();
}
else
{
return (byte*)scan0.ToInt32();
}
}
public unsafe void CodeImage(IntPtr scan0, Rectangle scanArea, Size imageSize, PixelFormat format, Stream outStream)
{
if (_disposed) throw new ObjectDisposedException("UnsafeStreamCodec");
if (!outStream.CanWrite) throw new ArgumentException("Stream must be writable", "outStream");
lock (_imageProcessLock)
{
byte* pScan0 = GetScan0Pointer(scan0);
int pixelSize = GetPixelSize(format);
int stride = imageSize.Width * pixelSize;
int rawLength = stride * imageSize.Height;
if (_encodeBuffer == null)
{
InitializeEncodeBuffer(scan0, imageSize, format, stride, rawLength, outStream);
return;
}
ValidateImageFormat(format, imageSize);
long startPosition = outStream.Position;
outStream.Write(new byte[4], 0, 4);
ProcessImageBlocks(pScan0, scanArea, stride, pixelSize, outStream);
long totalLength = outStream.Position - startPosition - 4;
outStream.Position = startPosition;
outStream.Write(BitConverter.GetBytes(totalLength), 0, 4);
outStream.Position = startPosition + 4 + totalLength;
}
}
private unsafe void InitializeEncodeBuffer(IntPtr scan0, Size imageSize, PixelFormat format,
int stride, int rawLength, Stream outStream)
{
this._encodedFormat = format;
this._encodedWidth = imageSize.Width;
this._encodedHeight = imageSize.Height;
this._encodeBuffer = new byte[rawLength];
fixed (byte* ptr = _encodeBuffer)
{
using (Bitmap tmpBmp = new Bitmap(imageSize.Width, imageSize.Height, stride, format, scan0))
{
byte[] compressed = _jpgCompression.Compress(tmpBmp);
outStream.Write(BitConverter.GetBytes(compressed.Length), 0, 4);
outStream.Write(compressed, 0, compressed.Length);
NativeMethods.memcpy(new IntPtr(ptr), scan0, (uint)rawLength);
}
}
}
private void ValidateImageFormat(PixelFormat format, Size imageSize)
{
if (this._encodedFormat != format)
throw new InvalidOperationException("PixelFormat differs from previous bitmap");
if (this._encodedWidth != imageSize.Width || this._encodedHeight != imageSize.Height)
throw new InvalidOperationException("Bitmap dimensions differ from previous bitmap");
}
private unsafe void ProcessImageBlocks(byte* pScan0, Rectangle scanArea, int stride,
int pixelSize, Stream outStream)
{
_workingBlocks.Clear();
_finalUpdates.Clear();
fixed (byte* encBuffer = _encodeBuffer)
{
DetectChangedRows(encBuffer, pScan0, scanArea, stride, pixelSize);
DetectChangedColumns(encBuffer, pScan0, scanArea, stride, pixelSize);
WriteCompressedBlocks(pScan0, stride, pixelSize, outStream);
}
}
private unsafe void DetectChangedRows(byte* encBuffer, byte* pScan0, Rectangle scanArea,
int stride, int pixelSize)
{
Size blockSize = new Size(scanArea.Width, CheckBlock.Height);
Size lastSize = new Size(scanArea.Width % CheckBlock.Width, scanArea.Height % CheckBlock.Height);
int lastY = scanArea.Height - lastSize.Height;
for (int y = scanArea.Y; y < scanArea.Height; y += blockSize.Height)
{
if (y == lastY)
blockSize = new Size(scanArea.Width, lastSize.Height);
Rectangle currentBlock = new Rectangle(scanArea.X, y, scanArea.Width, blockSize.Height);
int offset = (y * stride) + (scanArea.X * pixelSize);
if (NativeMethods.memcmp(encBuffer + offset, pScan0 + offset, (uint)stride) != 0)
{
if (_workingBlocks.Count > 0)
{
int lastIndex = _workingBlocks.Count - 1;
Rectangle lastBlock = _workingBlocks[lastIndex];
if (lastBlock.Y + lastBlock.Height == currentBlock.Y)
{
_workingBlocks[lastIndex] = new Rectangle(lastBlock.X, lastBlock.Y,
lastBlock.Width, lastBlock.Height + currentBlock.Height);
continue;
}
}
_workingBlocks.Add(currentBlock);
}
}
}
private unsafe void DetectChangedColumns(byte* encBuffer, byte* pScan0, Rectangle scanArea,
int stride, int pixelSize)
{
Size lastSize = new Size(scanArea.Width % CheckBlock.Width, scanArea.Height % CheckBlock.Height);
int lastX = scanArea.Width - lastSize.Width;
for (int i = 0; i < _workingBlocks.Count; i++)
{
Rectangle block = _workingBlocks[i];
Size columnSize = new Size(CheckBlock.Width, block.Height);
for (int x = scanArea.X; x < scanArea.Width; x += columnSize.Width)
{
if (x == lastX)
columnSize = new Size(lastSize.Width, block.Height);
Rectangle currentBlock = new Rectangle(x, block.Y, columnSize.Width, block.Height);
if (HasBlockChanged(encBuffer, pScan0, currentBlock, stride, pixelSize))
{
UpdateEncodeBuffer(encBuffer, pScan0, currentBlock, stride, pixelSize);
MergeOrAddBlock(currentBlock);
}
}
}
}
private unsafe bool HasBlockChanged(byte* encBuffer, byte* pScan0, Rectangle block,
int stride, int pixelSize)
{
uint blockStride = (uint)(pixelSize * block.Width);
for (int j = 0; j < block.Height; j++)
{
int blockOffset = (stride * (block.Y + j)) + (pixelSize * block.X);
if (NativeMethods.memcmp(encBuffer + blockOffset, pScan0 + blockOffset, blockStride) != 0)
return true;
}
return false;
}
private unsafe void UpdateEncodeBuffer(byte* encBuffer, byte* pScan0, Rectangle block,
int stride, int pixelSize)
{
uint blockStride = (uint)(pixelSize * block.Width);
for (int j = 0; j < block.Height; j++)
{
int blockOffset = (stride * (block.Y + j)) + (pixelSize * block.X);
NativeMethods.memcpy(encBuffer + blockOffset, pScan0 + blockOffset, blockStride);
}
}
private void MergeOrAddBlock(Rectangle currentBlock)
{
if (_finalUpdates.Count > 0)
{
int lastIndex = _finalUpdates.Count - 1;
Rectangle lastBlock = _finalUpdates[lastIndex];
if (lastBlock.X + lastBlock.Width == currentBlock.X && lastBlock.Y == currentBlock.Y)
{
_finalUpdates[lastIndex] = new Rectangle(lastBlock.X, lastBlock.Y,
lastBlock.Width + currentBlock.Width, lastBlock.Height);
return;
}
}
_finalUpdates.Add(currentBlock);
}
private unsafe void WriteCompressedBlocks(byte* pScan0, int stride, int pixelSize, Stream outStream)
{
for (int i = 0; i < _finalUpdates.Count; i++)
{
WriteCompressedBlock(pScan0, _finalUpdates[i], stride, pixelSize, outStream);
}
}
private unsafe void WriteCompressedBlock(byte* pScan0, Rectangle rect, int stride,
int pixelSize, Stream outStream)
{
int blockStride = pixelSize * rect.Width;
Array.Copy(BitConverter.GetBytes(rect.X), 0, _metadataBuffer, 0, 4);
Array.Copy(BitConverter.GetBytes(rect.Y), 0, _metadataBuffer, 4, 4);
Array.Copy(BitConverter.GetBytes(rect.Width), 0, _metadataBuffer, 8, 4);
Array.Copy(BitConverter.GetBytes(rect.Height), 0, _metadataBuffer, 12, 4);
outStream.Write(_metadataBuffer, 0, 20);
long lengthPosition = outStream.Position - 4;
long dataStart = outStream.Position;
Bitmap tmpBmp = null;
BitmapData tmpData = null;
try
{
tmpBmp = new Bitmap(rect.Width, rect.Height, this._encodedFormat);
tmpData = tmpBmp.LockBits(new Rectangle(0, 0, rect.Width, rect.Height),
ImageLockMode.ReadWrite, tmpBmp.PixelFormat);
CopyBlockData(pScan0, (byte*)tmpData.Scan0.ToPointer(), rect, stride, blockStride);
_jpgCompression.Compress(tmpBmp, outStream);
}
finally
{
if (tmpData != null)
tmpBmp.UnlockBits(tmpData);
if (tmpBmp != null)
tmpBmp.Dispose();
}
long dataLength = outStream.Position - dataStart;
long currentPosition = outStream.Position;
outStream.Position = lengthPosition;
outStream.Write(BitConverter.GetBytes(dataLength), 0, 4);
outStream.Position = currentPosition;
}
private unsafe void CopyBlockData(byte* source, byte* destination, Rectangle rect,
int stride, int blockStride)
{
for (int j = 0, offset = 0; j < rect.Height; j++)
{
int blockOffset = (stride * (rect.Y + j)) + (rect.X * GetPixelSize(this._encodedFormat));
NativeMethods.memcpy(destination + offset, source + blockOffset, (uint)blockStride);
offset += blockStride;
}
}
public unsafe Bitmap DecodeData(IntPtr codecBuffer, uint length)
{
if (_disposed) throw new ObjectDisposedException("UnsafeStreamCodec");
if (length < 4) return _decodedBitmap;
int dataSize = *(int*)codecBuffer;
if (_decodedBitmap == null)
{
byte[] temp = new byte[dataSize];
fixed (byte* tempPtr = temp)
{
NativeMethods.memcpy(new IntPtr(tempPtr),
new IntPtr(codecBuffer.ToInt32() + 4), (uint)dataSize);
}
using (MemoryStream stream = new MemoryStream(temp, 0, dataSize))
{
this._decodedBitmap = (Bitmap)Bitmap.FromStream(stream);
}
}
return _decodedBitmap;
}
public Bitmap DecodeData(Stream inStream)
{
if (_disposed) throw new ObjectDisposedException("UnsafeStreamCodec");
inStream.Read(_lengthBuffer, 0, 4);
int dataSize = BitConverter.ToInt32(_lengthBuffer, 0);
if (_decodedBitmap == null)
{
byte[] temp = new byte[dataSize];
inStream.Read(temp, 0, dataSize);
using (MemoryStream stream = new MemoryStream(temp, 0, dataSize))
{
this._decodedBitmap = (Bitmap)Bitmap.FromStream(stream);
}
return _decodedBitmap;
}
using (Graphics graphics = Graphics.FromImage(_decodedBitmap))
{
DecodeBlocks(inStream, graphics, dataSize);
}
return _decodedBitmap;
}
private void DecodeBlocks(Stream inStream, Graphics graphics, int remainingData)
{
while (remainingData > 0)
{
inStream.Read(_metadataBuffer, 0, 20);
Rectangle rect = new Rectangle(
BitConverter.ToInt32(_metadataBuffer, 0),
BitConverter.ToInt32(_metadataBuffer, 4),
BitConverter.ToInt32(_metadataBuffer, 8),
BitConverter.ToInt32(_metadataBuffer, 12));
int updateLength = BitConverter.ToInt32(_metadataBuffer, 16);
byte[] buffer = new byte[updateLength];
inStream.Read(buffer, 0, updateLength);
using (MemoryStream stream = new MemoryStream(buffer, 0, updateLength))
using (Bitmap bitmap = (Bitmap)Image.FromStream(stream))
{
graphics.DrawImage(bitmap, rect.Location);
}
remainingData -= updateLength + 20; // 20 bytes metadata
}
}
}
}
@@ -0,0 +1,59 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace Pulsar.Common.Video.Compression
{
public class JpgCompression : IDisposable
{
private static readonly ImageCodecInfo JpegEncoderInfo = GetEncoderInfoStatic();
private readonly EncoderParameters _encoderParams;
public JpgCompression(long quality)
{
EncoderParameter parameter = new EncoderParameter(Encoder.Quality, quality);
_encoderParams = new EncoderParameters(1);
_encoderParams.Param[0] = parameter;
}
public void Dispose()
{
if (_encoderParams != null)
{
_encoderParams.Dispose();
}
GC.SuppressFinalize(this);
}
public byte[] Compress(Bitmap bmp)
{
if (bmp == null) throw new ArgumentNullException(nameof(bmp));
using (MemoryStream stream = new MemoryStream())
{
bmp.Save(stream, JpegEncoderInfo, _encoderParams);
return stream.ToArray();
}
}
public void Compress(Bitmap bmp, Stream targetStream)
{
if (bmp == null) throw new ArgumentNullException(nameof(bmp));
if (targetStream == null) throw new ArgumentNullException(nameof(targetStream));
bmp.Save(targetStream, JpegEncoderInfo, _encoderParams);
}
private static ImageCodecInfo GetEncoderInfoStatic()
{
ImageCodecInfo[] imageEncoders = ImageCodecInfo.GetImageEncoders();
for (int i = 0; i < imageEncoders.Length; i++)
{
if (imageEncoders[i].MimeType == "image/jpeg")
{
return imageEncoders[i];
}
}
throw new InvalidOperationException("JPEG encoder not found");
}
}
}
+50
View File
@@ -0,0 +1,50 @@
using MessagePack;
using System;
namespace Pulsar.Common.Video
{
[MessagePackObject]
public class Resolution : IEquatable<Resolution>
{
[Key(1)]
public int Width { get; set; }
[Key(2)]
public int Height { get; set; }
public bool Equals(Resolution other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Width == other.Width && Height == other.Height;
}
public static bool operator ==(Resolution r1, Resolution r2)
{
if (ReferenceEquals(r1, null))
return ReferenceEquals(r2, null);
return r1.Equals(r2);
}
public static bool operator !=(Resolution r1, Resolution r2)
{
return !(r1 == r2);
}
public override bool Equals(object obj)
{
return Equals(obj as Resolution);
}
public override int GetHashCode()
{
return Width ^ Height;
}
public override string ToString()
{
return $"{Width}x{Height}";
}
}
}