Просмотр полной версии : Усредненный цвет области изображения
Подскажите, пожалуйста, как получить некое среднее значение цвета в Битмапдате или загруженном изображении?
Т.е, например, есть у меня изображение, и я хочу рамку для него перекрашивать в цвет подходящий для этой фотки.
Может кто алгоритмом поможет, может есть уже готовое решение.
Можно посчитать именно среднее - сложить 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
вот что у меня получилось методом "тупого" нахождения средних значений цветовых компонент
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-ку, пока не очень хочется на нее ориентироваться....
Попробуйте всё-же уменьшать изображение перед циклом, а также не перебирать точки в центре изображения (только края некоторой ширины).
Можно рисовать битмап в другой, который в 2 раза меньше, потом полученный битмап опять рисовать в уменьшенный в два раза, и т.д. пока не получишь изображение в 1 пиксел =)
А можно вообще уменьшить до 1 пикселя и взять его цвет :)
>>и т.д. пока не получишь изображение в 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;
}
mre, silin не, так не сработает, надо постепенно в два раза уменьшать :)
Кстати, в играх так расчитывается средняя освещенность сцены, когда используется HDR с адаптацией
Вобще, этот метод имеет смысл использовать если рисование картинки происходит при помощи видяхи, а не процессора. Вроде как в 9 и 10 плеере хардварное рисование, поэтому пусть ГПУ сам считает средний цвет =)
Вроде как в 9 и 10 плеере хардварное рисование
«Вроде как», но действительности не соответствует.
_dm, не понятно тебя: в чем нюанс, что значит "не сработает" ?, у меня почему-то работает
причем тут хардварное рисование тоже не совсем ясно, нативный draw по-любому будет быстрее скриптового счета, а реализуется он программно или на видеокарте - вопрос десятый
Не сработает, потому что при уменьшении не усредняются значения всех точек.
>>Не сработает, потому что при уменьшении не усредняются значения всех точек.
ага, а только некоторых :)
возможно и не усредняются в прямом смысле, на глаз это выглядит именно так
>>_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
Всем спасибо.
Метод 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;
}
}
}
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 );
так вроде работает, правильно ли это?
Надо умножить значения RGB на число от 0 до 1 (1 полная яркость).
Вот что по итогу получилось, может кому пригодится
/**
* Возвращает цвет темнее заданного на указанную величину
* @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, правильно правильно. забыл обратно преобразовать.
Работает на vBulletin ® версия 3.7.3. Copyright ©2000-2026, Jelsoft Enterprises Ltd. Перевод: zCarot
Copyright © 1999-2008 Flasher.ru. All rights reserved.