PDA

Просмотр полной версии : Усредненный цвет области изображения


Mur4ik
03.04.2009, 01:56
Подскажите, пожалуйста, как получить некое среднее значение цвета в Битмапдате или загруженном изображении?

Т.е, например, есть у меня изображение, и я хочу рамку для него перекрашивать в цвет подходящий для этой фотки.

Может кто алгоритмом поможет, может есть уже готовое решение.

VVall
03.04.2009, 02:06
Можно посчитать именно среднее - сложить RGB-значения всех точек в заданной области и поделить на количество точек. До этого можно уменьшить изображение с интерполяцией во временный буфер, чтобы перебирать меньше точек.

serenkiy
03.04.2009, 03:55
Можно использовать метод

public function histogram(hRect:Rectangle = null):Vector.<Vector>

у BitmapData. Он представляет распространение отдельных значений цветовых компонентов изображения (красный, зеленый, синий, альфа). Исходя из гистограммы, находим наиболее распространенное значение компонента (например, красный - AA, зеленый - BB, синий - CC...альфа не трогаем). Совмещаем шестнадцатеричные значения и получаем усредненный цвет - 0xAABBCC. Далее или используем его, или находим его контрастный цвет.
Нахождение контрастного цвета - отнимаем от 0xFFFFFF(белый цвет) найденное значение (в нашем случае 0xAABBCC), итоговый цвет - контрастный (у нас он равен 0x554433).

Добавлено через 4 минуты
http://f.imagehost.org/0373/Colors.png

Mur4ik
03.04.2009, 07:32
вот что у меня получилось методом "тупого" нахождения средних значений цветовых компонент
public function findFrameColour( $source:BitmapData ):uint
{
var r:Number = 0;
var g:Number = 0;
var b:Number = 0;

var pCount:Number = $source.width * $source.height;
var pixel:Number;

for (var px:int = 0; px < $source.width; px++)
{
for (var py:int = 0; py < $source.height; py++)
{
pixel = $source.getPixel(px, py);

r += pixel >> 16 & 0xFF;
g += pixel >> 8 & 0xFF;
b += pixel & 0xFF;
}
}

r /= pCount;
g /= pCount;
b /= pCount;

return r << 16 | g << 8 | b;
}

Но не всегда он выдает нормальный результат.
Может есть более гармоничный (в плане результирующего цвета) алгоритм?

С histogram() идея понятна, но это все таки под 10-ку, пока не очень хочется на нее ориентироваться....

VVall
03.04.2009, 07:49
Попробуйте всё-же уменьшать изображение перед циклом, а также не перебирать точки в центре изображения (только края некоторой ширины).

_dm
03.04.2009, 13:51
Можно рисовать битмап в другой, который в 2 раза меньше, потом полученный битмап опять рисовать в уменьшенный в два раза, и т.д. пока не получишь изображение в 1 пиксел =)

mre
03.04.2009, 14:37
А можно вообще уменьшить до 1 пикселя и взять его цвет :)

silin
03.04.2009, 14:46
>>и т.д. пока не получишь изображение в 1 пиксел =)
а чего не сразу-то
public function findFrameColour( source:BitmapData ):uint
{
var mtrx:Matrix = new Matrix();
mtrx.scale(1 / source.width, 1 / source.height);
var resBmd:BitmapData = new BitmapData(1, 1);
resBmd.draw(source, mtrx);
var res:uint = resBmd.getPixel(0, 0);
resBmd.dispose();
return res;
}

_dm
03.04.2009, 14:47
mre, silin не, так не сработает, надо постепенно в два раза уменьшать :)
Кстати, в играх так расчитывается средняя освещенность сцены, когда используется HDR с адаптацией

Вобще, этот метод имеет смысл использовать если рисование картинки происходит при помощи видяхи, а не процессора. Вроде как в 9 и 10 плеере хардварное рисование, поэтому пусть ГПУ сам считает средний цвет =)

etc
03.04.2009, 14:53
Вроде как в 9 и 10 плеере хардварное рисование
«Вроде как», но действительности не соответствует.

silin
03.04.2009, 15:23
_dm, не понятно тебя: в чем нюанс, что значит "не сработает" ?, у меня почему-то работает
причем тут хардварное рисование тоже не совсем ясно, нативный draw по-любому будет быстрее скриптового счета, а реализуется он программно или на видеокарте - вопрос десятый

VVall
03.04.2009, 15:26
Не сработает, потому что при уменьшении не усредняются значения всех точек.

silin
03.04.2009, 15:59
>>Не сработает, потому что при уменьшении не усредняются значения всех точек.
ага, а только некоторых :)
возможно и не усредняются в прямом смысле, на глаз это выглядит именно так

_dm
03.04.2009, 16:11
>>_dm, не понятно тебя: в чем нюанс, что значит "не сработает" ?, у меня почему-то работает
Если честно, я не пробовал рисовать сразу в один пиксель, просто если уменьшать в 2 раза, так будут усреднятся именно соседние пиксели, а если сразу в надцать раз уменьшить - некоторые точки не будут учтены... но если точность не важна, то можно сразу рисовать в 1 пиксель =)

З.Ы. погуглил о хардварном ускорении - в адобовском блоге написано "Starting in version 9.0.115.0, the Flash Player was able to display _fullscreen_ content with GPU assistance."... эх, а я думал оно и в окне ускоряет :(

serenkiy
03.04.2009, 16:17
Как я и предлагал, вот пример работы с гистограммой.

Класс "подборщика" цвета:

package
{
import flash.display.BitmapData;

public final class ColorPickup
{

public function ColorPickup(){}

// Функция нахождения контрастного цвета исходя из цвета
public static function pickedForColor(color:uint):uint
{
return (0xFFFFFF - color);
}

// Функция нахождения цвета исходя из изображения
public static function pickedForBitmap(bitmapData:BitmapData,contrast:Boolean):uint
{
var histogram:Vector.<Vector.<Number>> = bitmapData.histogram(),
rmax:uint=0,
gmax:uint=0,
bmax:uint=0,
r:uint=0,
g:uint=0,
b:uint=0,
color:uint;

for (var i:uint = 0; i < 256; i++) {
if (rmax < histogram[0][i]) {
rmax = histogram[0][i];
r = i;
}

if (gmax < histogram[1][i]) {
gmax = histogram[1][i];
g = i;
}

if (bmax < histogram[2][i]) {
bmax = histogram[2][i];
b = i;
}
}
color = ((r << 16) | (g << 8) | b);
if(contrast) color=pickedForColor(color);
return color;
}
}
}


Тестируем:


package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;

public class project extends Sprite
{
[Embed(source = 'testimage.jpg')] private var TestImage:Class;
private var bitmap:Bitmap = new TestImage();

public function project()
{
addChild(bitmap);
bitmap.x = (stage.stageWidth - bitmap.width)/2;
bitmap.y = (stage.stageHeight - bitmap.height) / 2;

var color:uint = ColorPickup.pickedForBitmap(bitmap.bitmapData, false);

graphics.beginFill(color);
graphics.drawRect(bitmap.x - 10, bitmap.y - 10, bitmap.width + 20, bitmap.height + 20);
graphics.endFill();
}
}

}


Если в функции pickedForBitmap второй параметр (contrast) будет false, то вернется "усредненный" цвет.

пример:
http://g.imagehost.org/0553/contrast.jpg

Mur4ik
03.04.2009, 19:39
Всем спасибо.
Метод silin'a тоже выдает не всегда приемлемые результаты (видимо это похоже просто на случайный выбор цвета одного пикселя), например на вечерних фотках, цвет всегда почти черный.
Метод serenkiy с хистограммой, наверное, самый правильный, но он под 10 плеер, "мне ща так низя" :)
Поэтому решил все таки искать средний цвет перебором чуток уменьшенного изображения.
Либо найти 4-8 средних цветов изображения и из них уже выбрать случайно один.

Вот еще осталось непонятно как сделать цвет чуть-чуть темнее?
Т.е не накладывать его с прозрачностью на темный фон или сверху него класть прозрачный темный фон, а именно сам цвет (т.е значение сделать темнее).

BlooDHounD
03.04.2009, 19:55
станет потемнее :)color = ( ( color ) & 0xFF - 0x33 ) |
( ( color >> 8 ) & 0xFF - 0x33 ) |
( ( color >> 16 ) & 0xFF - 0x33 );

serenkiy
04.04.2009, 00:35
Ну, раз используется 9я версия, переписал класс для 9й версии:

package
{
import flash.display.BitmapData;
import flash.utils.ByteArray;

public final class ColorPickup
{

public function ColorPickup(){}

// Функция нахождения контрастного цвета исходя из цвета
public static function pickedForColor(color:uint):uint
{
return (0xFFFFFF - color);
}

// Функция нахождения цвета исходя из изображения
public static function pickedForBitmap(bitmapData:BitmapData,contrast:Boolean):uint
{
var histogram:Array = getHistogram(bitmapData),
rmax:uint = 0,
gmax:uint = 0,
bmax:uint = 0,
r:uint = 0,
g:uint = 0,
b:uint = 0,
color:uint;

for (var i:uint = 0; i < 256; i++) {
if (rmax < histogram[0][i]) {
rmax = histogram[0][i];
r = i;
}

if (gmax < histogram[1][i]) {
gmax = histogram[1][i];
g = i;
}

if (bmax < histogram[2][i]) {
bmax = histogram[2][i];
b = i;
}
}
color = ((r << 16) | (g << 8) | b);
if (contrast) color = pickedForColor(color);
return color;
}

// Функция нахождения гистограммы изображения
public static function getHistogram(bitmapData:BitmapData):Array
{
var bytes:ByteArray = bitmapData.getPixels(bitmapData.rect),
array:Array = new Array(3),
currentByte:int,
r:int,
g:int,
b:int;
array[0] = new Array(256);
array[1] = new Array(256);
array[2] = new Array(256);
for (var i:uint = 0; i < 256; i++) {
array[0][i] = array[1][i] = array[2][i] = 0;
}
bytes.position = 0;
while (bytes.bytesAvailable != 0) {
currentByte = bytes.readUnsignedInt();
r = (currentByte & 0x00FF0000) >> 16;
g = (currentByte & 0x0000FF00) >> 8;
b = (currentByte & 0x000000FF);
array[0][r] += 1;
array[1][g] += 1;
array[2][b] += 1;
}
return array;

}
}
}

Mur4ik
04.04.2009, 03:41
serenkiy, спасибо за класс, пригодится ;)

Вообщем методом проб и ошибок пришел к выводу, что наилучшим является метод с вычислением среднего значения цвета всех точек, а метод гистограмм чаще него выдает результат не подходящий к фотке.
Метод silin'a берет некий цвет прямо из центральной точки фотки (остальные не учитываются), наверное этот способ равносилен просто выборке цвета этой точки :).

Всем спасибо.

станет потемнее :)color = ( ( color ) & 0xFF - 0x33 ) |
( ( color >> 8 ) & 0xFF - 0x33 ) |
( ( color >> 16 ) & 0xFF - 0x33 );

чет не работает так :)

Но как же все таки сделать цвет потемнее?

Наверное BlooDHounD имел ввиду что то типа такого:


frameColor = ( ( frameColor >> 16 ) & 0xFF - 0x33 ) << 16 |
( ( frameColor >> 8 ) & 0xFF - 0x33 ) << 8 |
( ( frameColor ) & 0xFF - 0x33 );

так вроде работает, правильно ли это?

VVall
04.04.2009, 03:54
Надо умножить значения RGB на число от 0 до 1 (1 полная яркость).

Mur4ik
04.04.2009, 04:42
Вот что по итогу получилось, может кому пригодится

/**
* Возвращает цвет темнее заданного на указанную величину
* @param $color Исходный цвет
* @param $koeff На сколько делать темнее
* @return Результирующий цвет
*/
public static function darker( $color:uint, $koeff:Number = .7 ):uint
{
var r:Number = 0;
var g:Number = 0;
var b:Number = 0;

r = ($color >> 16 & 0xFF) * $koeff;
g = ($color >> 8 & 0xFF) * $koeff;
b = ($color & 0xFF) * $koeff;

return r << 16 | g << 8 | b;
}

/**
* Находит среднее значение цвета всего изображения
* @param $source Исходное изображение
* @param $scale масштаб изображения для подсчета цвета
* @return результирующий цвет
*/
public static function findMedianColour( $source:BitmapData, $scale:Number = .25 ):uint
{
var xScale:uint = Math.round( $source.width * $scale );
var yScale:uint = Math.round( $source.height * $scale );

var mtrx:Matrix = new Matrix();
var tempBmd:BitmapData = new BitmapData(xScale, yScale);

mtrx.scale($scale,$ scale);
tempBmd.draw($source, mtrx);

var r:uint = 0;
var g:uint = 0;
var b:uint = 0;

var pCount:uint = xScale * yScale;
var pixel:uint;

for (var px:int = 0; px < xScale; px++)
{
for (var py:int = 0; py < yScale; py++)
{
pixel = tempBmd.getPixel(px, py);

r += pixel >> 16 & 0xFF;
g += pixel >> 8 & 0xFF;
b += pixel & 0xFF;
}
}

tempBmd.dispose();

r /= pCount;
g /= pCount;
b /= pCount;

return r << 16 | g << 8 | b;
}


Поправил функцию darker, c умножением каждой компоненты затемнение выглядит более правильно.

BlooDHounD
04.04.2009, 15:48
Mur4ik, правильно правильно. забыл обратно преобразовать.