Как я могу скопировать данные пикселей из растрового изображения с отрицательным шагом?
Я искал самый быстрый способ конвертировать растровое изображение в 8bpp. Я нашел 2 способа:
1.
public static System.Drawing.Image ConvertTo8bpp(Bitmap oldbmp)
{
using (var ms = new MemoryStream())
{
oldbmp.Save(ms, ImageFormat.Gif);
ms.Position = 0;
return System.Drawing.Image.FromStream(ms);
}
}
2. http://www.wischik.com/lu/programmer/1bpp.html
Но: 1. В результате получается очень низкое качество (плохой поддон)
и 2 дает мне растровое изображение с отрицательным шагом, когда я пытаюсь заблокировать и копировать данные в байтовый массив, я получаю исключение: Попытка чтения или записи в защищенную память. Это часто указывает на то, что другая память повреждена.
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
this.stride = bmpData.Stride;
this.bytesPerPixel = GetBytesPerPixel(bmp.PixelFormat);
int length = bmpData.Stride * bmp.Height;
if (this.stride < 0)
this.data = new byte[-length];
else
this.data = new byte[length];
Marshal.Copy(bmpData.Scan0, data, 0, length);
//Unlock the bitmap
bmp.UnlockBits(bmpData);
Как я могу сделать 2 дает положительный шаг? Или как я могу скопировать данные, используя блокировку отрицательного шага?
6 ответов
Копировать 1 строку за раз, вычисляя начальный указатель для строки как ((byte*)scan0 + (y * stride))
, Код будет одинаковым для положительного или отрицательного шага.
Проблема здесь в том, что Scan0
указывает на начало первой строки сканирования, а не на начало первого байта данных. В восходящем растровом изображении первая строка сканирования Stride
байты от конца растровых данных.
Когда вы звоните Marshal.Copy
скопировать данные из Scan0
пытается скопировать (Height*Stride)
байты, начиная с позиции ((Height-1)*Stride)
, Понятно, что это сбежит в сорняки.
Если вы просто хотите скопировать растровые данные, вы должны рассчитать начальный адрес с Scan0 - (Height-1)*Stride
, Это начнет вас в начале растровых данных. Вы можете передать этот вычисленный адрес Marshal.Copy
,
Если вы хотите скопировать строки сканирования по порядку (т. Е. Top, next, next, ... bottom), вам нужно скопировать строку за раз: copy Stride
байты из Scan0
, затем добавьте Stride
(что отрицательно), скопируйте эту строку и т. д. У Рика Брюстера был правильный ответ: /questions/6151390/kak-ya-mogu-skopirovat-dannyie-pikselej-iz-rastrovogo-izobrazheniya-s-otritsatelnyim-shagom/6151397#6151397
Я не знаю, почему в растровом изображении, созданном FromHbitmap
метод, но я знаю, что вы можете это исправить с помощью Bitmap bmpClone = (Bitmap)bmp.Clone();
и делает LockBits на bmpClone.
Кроме того, я обнаружил, что если вы используете bmp.Clone()
Вы не можете удалить () из bmp, пока не покончили с клоном.
Это также работает, и давайте избавимся от негативного шага раньше, чем позже:
Bitmap bmp = null;
using (Bitmap bmpT = CopyToBpp(bmpO, 1))
{
bmp = new Bitmap(bmpT);
}
Из документации C# по BitmapData: шаг - это ширина одного ряда пикселей (строки сканирования), округленная до четырехбайтовой границы. Если шаг положительный, растровое изображение идет сверху вниз. Если шаг отрицательный, изображение растёт снизу вверх
Мне удалось решить эту проблему, не создавая совершенно новый объект Bitmap, используя
LockBits()
с
ImageLockMode.UserInputBuffer
. Прикрепил код, который я придумал, не стесняйтесь использовать.
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
public static class BitmapExtensions
{
public static ProperBitmapData LockBitsProper(this Bitmap bitmap, ImageLockMode flags)
{
Rectangle bitmapBounds = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
return bitmap.LockBitsProper(bitmapBounds, flags, bitmap.PixelFormat);
}
public static ProperBitmapData LockBitsProper(this Bitmap bitmap, Rectangle rect, ImageLockMode flags, PixelFormat format)
{
BitmapData bmpData = bitmap.LockBits(rect, flags, format);
int byteCount;
try
{
byteCount = Math.Abs(bmpData.Stride) * bmpData.Height;
if (bmpData.Stride > 0) return new ProperBitmapData(bitmap, bmpData, byteCount, IntPtr.Zero);
}
catch
{
bitmap.UnlockBits(bmpData);
throw;
}
// in case Stride is negative
bitmap.UnlockBits(bmpData);
// When Stride is negative, the LockBits locks the wrong memory area which results in AccessViolationException even when reading the right place
// starting with Scan0 + (Height - 1) * Stride (also not properly documented).
// This is a workaround to it using a user allocated area overload.
// For some reason, in Windows Vista (SP0) Stride is (almost?) always negative, while in >=Windows 7 it is positive more often.
// Some useful documentation: https://docs.microsoft.com/en-us/windows/win32/api/gdiplusheaders/nf-gdiplusheaders-bitmap-lockbits
IntPtr userAllocatedArea = Marshal.AllocHGlobal(byteCount);
try
{
// Actually when Stride is negative, Scan0 have to point to where the last row will be written.
// This is not properly documented anywhere, and discovered just by trial and error.
bmpData.Scan0 = (IntPtr)((long)userAllocatedArea - (bmpData.Height - 1) * bmpData.Stride);
bmpData = bitmap.LockBits(rect, ImageLockMode.UserInputBuffer | flags, format, bmpData);
try
{
return new ProperBitmapData(bitmap, bmpData, byteCount, userAllocatedArea);
}
catch
{
bitmap.UnlockBits(bmpData);
throw;
}
}
catch
{
Marshal.FreeHGlobal(userAllocatedArea);
throw;
}
}
}
public class ProperBitmapData : IDisposable
{
private Bitmap _bitmap;
private BitmapData _bitmapData;
private int _byteCount;
private IntPtr _userAllocatedBuffer;
public int Width => _bitmapData.Width;
public int Height => _bitmapData.Height;
public int Stride => _bitmapData.Stride;
public PixelFormat PixelFormat => _bitmapData.PixelFormat;
public IntPtr Scan0 => (_userAllocatedBuffer != IntPtr.Zero) ? _userAllocatedBuffer : _bitmapData.Scan0;
public int Reserved => _bitmapData.Reserved;
public int ByteCount => _byteCount;
public ProperBitmapData(Bitmap bitmap, BitmapData bitmapData, int byteCount, IntPtr userAllocatedBuffer)
{
_bitmap = bitmap;
_bitmapData = bitmapData;
_byteCount = byteCount;
_userAllocatedBuffer = userAllocatedBuffer;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
_bitmap?.UnlockBits(_bitmapData);
_bitmap = null;
_bitmapData = null;
if (_userAllocatedBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(_userAllocatedBuffer);
_userAllocatedBuffer = IntPtr.Zero;
}
}
~ProperBitmapData()
{
Dispose(false);
}
}
Пример использования:
using (ProperBitmapData bmpData = bitmap.LockBitsProper(ImageLockMode.ReadOnly))
{
// Beware that bmpData.Scan0 here always points to the start of the allocated memory block.
this.data = new byte[bmpData.ByteCount];
Marshal.Copy(bmpData.Scan0, data, 0, bmpData.ByteCount);
}
Я предполагаю, что исключение, которое вы получаете, связано с
this.data = new byte[-length];
А потом пытаюсь скопировать данные в байтовый массив отрицательного размера (я не вижу, как это даже компилируется на самом деле...).