2016-12-20 3 views
3

Это проблема:
откладывает Картинку в .png с цветами говорят ARGB(50,210,102,70) с размерностью 1 x 1 pixel.Неправильные значения RGB в GetPixel() из изображения PNG

Я снова получаю то же изображение и использую метод GetPixel(0,0), что я получаю ARGB(50,209,102,70).

Существует небольшое изменение в полученном значении, значения RGB немного отличаются, но значение A остается таким же.

Однако, когда я использую 255 для значения A, правильные значения RGB возвращаются.

Итак, .. Используя значение, меньшее 255 для A, возникает проблема, упомянутая выше.

Вот код, который сохраняет битмап.

Bitmap bmpPut = new Bitmap(1, 1); //Also tried with 'PixelFormat.Format32bppArgb' 
bmpPut.SetPixel(0, 0, Color.FromArgb(254, 220, 210, 70)); 
bmpPut.Save("1.png"); //Also tried with using 'ImageFormat.Png' 

Вот код, который получает цвет пикселя

Bitmap bit = new Bitmap(Image.FromFile("1.png")); 
MessageBox.Show("R:" + bit.GetPixel(0, 0).R.ToString() + 
    "| G: " + bit.GetPixel(0, 0).G.ToString() + 
    "| B: " + bit.GetPixel(0, 0).B.ToString() + 
    "| A: " + bit.GetPixel(0, 0).A.ToString()); 

Что я получаю ARGB(254,219,209,70)

enter image description here

P.S.: Я прочитал несколько подобных вопросов, они не рассматривали этот точный вопрос, и я пока не найду решение.

+1

Попробуйте использовать эту часть без 'Image.FromFile()': Bitmap bit = new Bitmap ("1.png"); '. Работал для меня. – mammago

+0

Я попробую и вернусь! –

+0

Ничего себе, это было довольно просто. Спасибо @mammago. Он работает сейчас! Вы можете сказать это как ответ! так что я могу отметить ответ! –

ответ

1

Замена

Bitmap bit = new Bitmap(Image.FromFile("1.png")); 

с

Bitmap bit = new Bitmap("1.png"); 

Если сделать трюк. Кажется, что Image.FromFile() не такой точный, как конструктор Bitmap.

+1

Это работает, но почему? и как? –

+1

есть объяснение для этого? – fubo

+1

Извините, но я не могу объяснить это поведение. Если вы хотите исследовать, вы можете взглянуть на реализацию обоих источников в System.Drawing. Я видел, что оба метода используют разные функции в базовой GDI + dll, но я не могу сказать вам, что именно происходит, потому что я не разработчик C++. Ссылки исходного кода на [Растровое изображение] (https://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Bitmap.cs,e1ba6ce9c451889d) и [Изображение] (https://referencesource.microsoft.com/ # System.Drawing/commonui/System/Drawing/Image.cs, 3ac4e4b806474215) – mammago

5

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, так как оно простое и правильное.

+0

+1, очень интересно читать. К сожалению, я сдался, когда достиг уровня C++, но приятно видеть, что на самом деле вызывает несоответствие. – mammago

+0

Это было много копать !! Спасибо за удивительное объяснение –

Смежные вопросы