mammago has found a workaround, а именно с помощью конструктора класса, чтобы построить Bitmap
объект непосредственно из файла, а не построение Bitmap
объекта косвенно через Image
объекта, возвращенный Image.FromFile()
.
Цель этого ответа, чтобы объяснить, почему , который работает, и, в частности, то, что фактическая разница между этими двумя подходами, которые вызывают быть получены различные значения на пиксель цвета.
Одним из предложений по разнице было управление цветом. Однако это, как представляется, не стартер, так как ни один вызов не требует поддержки управления цветом (ICM).
Вы можете, однако, рассказать об этом, проверив исходный код для .NET BCL. В a comment mammago разместил ссылки на код для реализации классов Image
и Bitmap
, но не смог распознать соответствующие различия.
Давайте начнем с Bitmap
class constructor that creates a Bitmap
object directly from a file, так как это самый простой:
public Bitmap(String filename) {
IntSecurity.DemandReadFileIO(filename);
// GDI+ will read this file multiple times. Get the fully qualified path
// so if our app changes default directory we won't get an error
filename = Path.GetFullPath(filename);
IntPtr bitmap = IntPtr.Zero;
int status = SafeNativeMethods.Gdip.GdipCreateBitmapFromFile(filename, out bitmap);
if (status != SafeNativeMethods.Gdip.Ok)
throw SafeNativeMethods.Gdip.StatusException(status);
status = SafeNativeMethods.Gdip.GdipImageForceValidation(new HandleRef(null, bitmap));
if (status != SafeNativeMethods.Gdip.Ok) {
SafeNativeMethods.Gdip.GdipDisposeImage(new HandleRef(null, bitmap));
throw SafeNativeMethods.Gdip.StatusException(status);
}
SetNativeImage(bitmap);
EnsureSave(this, filename, null);
}
Много вещей происходит там, но большинство из них не имеет значения. Первые биты кода просто получают и проверяют путь. После этого это важный бит: вызов собственной функции GDI +, GdipCreateBitmapFromFile
, один из the many Bitmap-related functions provided by the GDI+ flat API. Он делает именно то, что вы думаете, он создает объект Bitmap
из пути к файлу изображения без использования согласования цветов (ICM). Это функция, которая делает тяжелый подъем. Затем оболочка .NET проверяет наличие ошибок и снова проверяет результирующий объект. Если проверка не удалась, она очищает и выдает исключение. Если проверка завершается успешно, она сохраняет дескриптор в переменной-члене (вызов SetNativeImage
), а затем вызывает функцию (EnsureSave
), которая ничего не делает, кроме изображения, если GIF. Поскольку этого нет, мы полностью это проигнорируем.
Хорошо, так что концептуально это просто большая, дорогая обертка вокруг GdipCreateBitmapFromFile
, которая выполняет кучу избыточной проверки.
Как насчет Image.FromFile()
? Ну, the overload you're actually calling - это просто заглушка, которая пересылает the other overload, минуя false
, чтобы указать, что согласование цветов (ICM) нежелательно. Код для интересной перегрузки выглядит следующим образом:
public static Image FromFile(String filename,
bool useEmbeddedColorManagement) {
if (!File.Exists(filename)) {
IntSecurity.DemandReadFileIO(filename);
throw new FileNotFoundException(filename);
}
// GDI+ will read this file multiple times. Get the fully qualified path
// so if our app changes default directory we won't get an error
filename = Path.GetFullPath(filename);
IntPtr image = IntPtr.Zero;
int status;
if (useEmbeddedColorManagement) {
status = SafeNativeMethods.Gdip.GdipLoadImageFromFileICM(filename, out image);
}
else {
status = SafeNativeMethods.Gdip.GdipLoadImageFromFile(filename, out image);
}
if (status != SafeNativeMethods.Gdip.Ok)
throw SafeNativeMethods.Gdip.StatusException(status);
status = SafeNativeMethods.Gdip.GdipImageForceValidation(new HandleRef(null, image));
if (status != SafeNativeMethods.Gdip.Ok) {
SafeNativeMethods.Gdip.GdipDisposeImage(new HandleRef(null, image));
throw SafeNativeMethods.Gdip.StatusException(status);
}
Image img = CreateImageObject(image);
EnsureSave(img, filename, null);
return img;
}
Это выглядит очень похоже. Он несколько раз проверяет имя файла, но это не так, поэтому мы можем игнорировать эти различия. Если встроенное управление цветом не было запрошено, он делегирует другую функцию GDI + flat API для выполнения тяжелой работы: GdipLoadImageFromFile
.
Others have speculated что разница может быть результатом этих двух разных нативных функций. Это хорошая теория, но я разобрал эти функции, и хотя они имеют различные реализации, нет существенных различий, которые могли бы объяснить поведение, наблюдаемое здесь. GdipCreateBitmapFromFile
выполнит проверку, попытается загрузить метафайл, если это возможно, а затем вызовет конструктор для внутреннего класса GpBitmap
для выполнения фактической загрузки. GdipLoadImageFromFile
реализован аналогично, за исключением того, что он приходит к конструктору класса GpBitmap
косвенно через внутреннюю функцию GpImage::LoadImage
. Кроме того, я не смог воспроизвести описанное вами поведение, вызвав эти собственные функции непосредственно на C++, чтобы исключить их в качестве кандидатов для объяснения.
Интересно, что я также не смог воспроизвести поведение, вы можете описать литьем результат Image.FromFile
к Bitmap
, например:
Bitmap bit = (Bitmap)(Image.FromFile("1.png"));
Хотя не очень хорошая идея, чтобы полагаться на него, вы можете увидеть, что это на самом деле является законным, если вы вернетесь к исходному коду для Image.FromFile
. Он вызывает the internal CreateImageObject
function, который делегирует либо Bitmap.FromGDIplus
, либо Metafile.FromGDIplus
в соответствии с фактическим типом загружаемого изображения. The Bitmap.FromGDIplus
function просто создает объект Bitmap
, вызывает функцию SetNativeImage
, которую мы уже видели, чтобы установить ее базовый дескриптор, и возвращает этот объект Bitmap
. Поэтому, когда вы загружаете растровое изображение из файла, Image.FromFile
фактически возвращает объект Bitmap
. И этот объект Bitmap
ведет себя идентично тому, который создается с помощью конструктора класса Bitmap
.
Ключ к воспроизведению поведения является создание новогоBitmap
объекта на основе результата Image.FromFile
, который является то, что именно ваш исходный код сделал:
Bitmap bit = new Bitmap(Image.FromFile("1.png"));
Это будет вызывать the Bitmap
class constructor that takes an Image
object, который делегаты внутренне one that takes explicit dimensions:
public Bitmap(Image original, int width, int height) : this(width, height) {
Graphics g = null;
try {
g = Graphics.FromImage(this);
g.Clear(Color.Transparent);
g.DrawImage(original, 0, 0, width, height);
}
finally {
if (g != null) {
g.Dispose();
}
}
}
И здесь - это то, где мы наконец находим объяснение поведения, которое вы описываете в вопросе! Вы можете видеть, что создает временный объект Graphics
из указанного объекта Image
, заполняет объект Graphics
прозрачным цветом и, наконец, рисует копию указанного Image
в контексте Graphics
. На данный момент это не то же изображение, с которым вы работаете, но копия этого изображения. Это может привести к совпадению цветов, а также к множеству других вещей, которые могут повлиять на изображение.
В самом деле, в стороне от неожиданного поведения, описанного в вопросе, код, который вы написали скрыли ошибку: он не может распоряжаться временный Image
объект, созданный Image.FromFile
!
Тайна решена. Извиняюсь за длинный косвенный ответ, но, надеюсь, он научил вас чему-то об отладке! Продолжайте использовать решение, рекомендованное mammago, так как оно простое и правильное.
Попробуйте использовать эту часть без 'Image.FromFile()': Bitmap bit = new Bitmap ("1.png"); '. Работал для меня. – mammago
Я попробую и вернусь! –
Ничего себе, это было довольно просто. Спасибо @mammago. Он работает сейчас! Вы можете сказать это как ответ! так что я могу отметить ответ! –