最近项目中有一个需求:
美术给定一张图片,通过配置文件,在不同建筑等级时使用同一张图片,但是在不同等级时通过程序不断调整hsbc(photoshop中也可以调整)显示出不同的效果。通过搜索,有两种方法解决:
首先介绍下HSL和HSV、HSB:
HSL 和 HSV(也叫HSB)是对RGB 色彩空间中点的两种有关系的表示,它们尝试描述比 RGB 更准确的感知颜色联系,并仍保持在计算上简单。
H指hue(色相)、S指saturation(饱和度)、L指lightness(亮度)、V指value(色调)、B指brightness(明度)
这里的matrix其实是一个Array;
matrix:Array [read-write]
由 20 个项目组成的数组,适用于 4 x 5 颜色转换。
颜色矩阵滤镜将每个源像素分离成它的红色、绿色、蓝色和 Alpha 成分,分别以 srcR、srcG、srcB 和 srcA 表示。 若要计算四个通道中每个通道的结果,可将图像中每个像素的值乘以转换矩阵中的值。 (可选)可以将偏移量(介于 -255 至 255 之间)添加到每个结果(矩阵的每行中的第五项)中。 滤镜将各颜色成分重新组合为单一像素,并写出结果。 在下列公式中,a[0] 到 a[19] 对应于由 20 个项目组成的数组中的条目 0 至 19,该数组已传递到 matrix 属性:
redResult = (a[0] * srcR) + (a[1] * srcG) + (a[2] * srcB) + (a[3] * srcA) + a[4]
greenResult = (a[5] * srcR) + (a[6] * srcG) + (a[7] * srcB) + (a[8] * srcA) + a[9]
blueResult = (a[10] * srcR) + (a[11] * srcG) + (a[12] * srcB) + (a[13] * srcA) + a[14]
alphaResult = (a[15] * srcR) + (a[16] * srcG) + (a[17] * srcB) + (a[18] * srcA) + a[19]
对于数组中的每个颜色值,值 1 等于正发送到输出的通道的 100%,同时保留颜色通道的值。
#ifndef ADJUSTCOLOR_H_
#define ADJUSTCOLOR_H_
#include "ColorMatrix.h"
struct ColorHSB_T
{
int hue;
int saturation;
int brightness;
int contrast;
ColorHSB_T(int h = 0, int s = 0, int b = 0, int c = 0):hue(h),saturation(s),brightness(b),contrast(c)
{
}
};
#define PI (3.1415926535)
class AdjustColor
{
public:
AdjustColor();
~AdjustColor();
void setbrightness(float value);
void setcontrast(float value);
void setsaturation(float value);
void sethue(float value);
float** CalculateFinalFlatArray();
bool AllValuesAreSet();
bool CalculateFinalMatrix();
protected:
private:
static float s_arrayOfDeltaIndex[];
ColorMatrix* m_brightnessMatrix;
ColorMatrix* m_contrastMatrix;
ColorMatrix* m_saturationMatrix;
ColorMatrix* m_hueMatrix;
ColorMatrix* m_finalMatrix;
};
void ColorAdjust(ColorHSB_T& hsb, void* SrcData, int width, int height);
#endif
AdjustColor.cpp
#include "AdjustColor.h"
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
float** s_arr = NULL;
float AdjustColor::s_arrayOfDeltaIndex[]= {
// 0 1 2 3 4 5 6 7 8 9
/*0*/ 0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11,
/*1*/ 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24,
/*2*/ 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42,
/*3*/ 0.44, 0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68,
/*4*/ 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98,
/*5*/ 1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54,
/*6*/ 1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25,
/*7*/ 2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, 3.8,
/*8*/ 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0,
/*9*/ 7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 9.6, 9.8,
/*10*/ 10.0 };
AdjustColor::AdjustColor():
m_brightnessMatrix(NULL),
m_contrastMatrix(NULL),
m_saturationMatrix(NULL),
m_hueMatrix(NULL),
m_finalMatrix(NULL)
{
}
AdjustColor::~AdjustColor()
{
if (m_brightnessMatrix)
{
delete m_brightnessMatrix;
}
if(m_contrastMatrix)
{
delete m_contrastMatrix;
}
if (m_saturationMatrix)
{
delete m_saturationMatrix;
}
if (m_finalMatrix)
{
delete m_finalMatrix;
}
}
// AdjustColor* AdjustColor::GetInstance()
// {
// if (s_adjust)
// {
// s_adjust = new AdjustColor;
// }
// return s_adjust;
// }
void AdjustColor::setbrightness(float value)
{
if(m_brightnessMatrix == NULL)
{
m_brightnessMatrix = new ColorMatrix();
}
if(value != 0)
{
// brightness does not need to be denormalized
m_brightnessMatrix->SetBrightnessMatrix(value);
}
}
void AdjustColor::setcontrast(float value)
{
// denormalized contrast value
float deNormVal = value;
if(value == 0)
{
deNormVal = 127;
}
else if(value > 0)
{
deNormVal = s_arrayOfDeltaIndex[int(value)] * 127 + 127;
}
else
{
deNormVal = (value / 100 * 127) + 127;
}
if(m_contrastMatrix == NULL)
{
m_contrastMatrix = new ColorMatrix();
}
m_contrastMatrix->SetContrastMatrix(deNormVal);
}
void AdjustColor::setsaturation(float value)
{
// denormalized saturation value
float deNormVal = value;
if (value == 0)
{
deNormVal = 1;
}
else if (value > 0)
{
deNormVal = 1.0 + (3 * value / 100); // max value is 4
}
else
{
deNormVal = value / 100 + 1;
}
if(m_saturationMatrix == NULL)
{
m_saturationMatrix = new ColorMatrix();
}
m_saturationMatrix->SetSaturationMatrix(deNormVal);
}
void AdjustColor::sethue(float value)
{
// hue value does not need to be denormalized
if(m_hueMatrix == NULL)
{
m_hueMatrix = new ColorMatrix();
}
if(value != 0)
{
// Convert to radian
m_hueMatrix->SetHueMatrix(value * PI / 180.0);
}
}
bool AdjustColor::AllValuesAreSet()
{
return (m_brightnessMatrix && m_contrastMatrix && m_saturationMatrix && m_hueMatrix);
}
float** AdjustColor::CalculateFinalFlatArray()
{
if(CalculateFinalMatrix())
{
return m_finalMatrix->GetFlatArray();
}
return NULL;
}
bool AdjustColor::CalculateFinalMatrix()
{
if(!AllValuesAreSet())
return false;
if (!m_finalMatrix)
{
m_finalMatrix = new ColorMatrix();
}
m_finalMatrix->Multiply(*m_brightnessMatrix);
m_finalMatrix->Multiply(*m_contrastMatrix);
m_finalMatrix->Multiply(*m_saturationMatrix);
m_finalMatrix->Multiply(*m_hueMatrix);
return true;
}
void ColorAdjust(int brightness, int contrast, int saturation, int hue, void* SrcData, int width, int height)
{
if (NULL == SrcData)
{
return;
}
printf("ColorAdjustColorAdjustColorAdjustColorAdjustColorAdjustColorAdjustColorAdjust!!!\n");
AdjustColor s_adjustColor;
s_adjustColor.setbrightness(brightness);
s_adjustColor.setcontrast(contrast);
s_adjustColor.setsaturation(saturation);
s_adjustColor.sethue(hue);
float** arr = s_adjustColor.CalculateFinalFlatArray();
unsigned char* pData = (unsigned char*)SrcData;
for (long i =0; i < width * height * 4; i+=4)
{
int R,G,B,A;
R=pData[i];
G=pData[i+1];
B=pData[i+2];
A=pData[i+3];
if (0 == A)
{
continue;
}
for(int j = 0; j < 3; j++)
{
int res = arr[j][0]*R + arr[j][1]*G + arr[j][2]*B +arr[j][3]*A + arr[j][4];
if (res < 0)
{
res = 0;
}
if (res > 255)
{
res = 255;
}
pData[i + j] = res;
}
}
}
AdjustColor s_adjustColor;
void SetColorAdjust(int brightness, int contrast, int saturation, int hue)
{
s_adjustColor.setbrightness(brightness);
s_adjustColor.setcontrast(contrast);
s_adjustColor.setsaturation(saturation);
s_adjustColor.sethue(hue);
s_arr = s_adjustColor.CalculateFinalFlatArray();
}
float GetResult(int row, int col)
{
float val = 0;
if (s_arr && row < 5 && col < 5)
{
val = s_arr[row][col];
}
return val;
}
#ifndef COLORMATRIE_H_
#define COLORMATRIE_H_
#include <stdlib.h>
#if defined COLORMATRIX_DLL_EXPORT
#define COLORMATRIX_DECLDIR __declspec(dllexport)
#else
#define COLORMATRIX_DECLDIR __declspec(dllimport)
#endif
class COLORMATRIX_DECLDIR DynamicMatrix
{
public:
static const int MATRIX_ORDER_PREPEND = 0;
static const int MATRIX_ORDER_APPEND = 1;
DynamicMatrix(int w, int h);
~DynamicMatrix();
int GetWidth();
int GetHeight();
float GetValue(int row, int col);
void SetValue(int row, int col, float value);
void LoadIdentity();
void LoadZeros();
bool Multiply(DynamicMatrix& inMatrix, int order = MATRIX_ORDER_PREPEND);
bool MultiplyNumber(float value);
bool Add(DynamicMatrix&inMatrix);
protected:
void Create(int width, int height);
void Destroy();
int m_width;
int m_height;
float** m_matrix;
};
class ColorMatrix:public DynamicMatrix
{
public:
ColorMatrix();
void SetBrightnessMatrix(float value);
void SetContrastMatrix(float value);
void SetSaturationMatrix(float value);
void SetHueMatrix(float angle);
float** GetFlatArray();
protected:
static float LUMINANCER;
static float LUMINANCEG;
static float LUMINANCEB;
};
#endif
#include "ColorMatrix.h"
#include <math.h>
int max(int a, int b)
{
return a > b ? a:b;
}
DynamicMatrix::DynamicMatrix(int w, int h)
{
m_width = 0;
m_height = 0;
m_matrix = NULL;
Create(w, h);
}
void DynamicMatrix::Create(int width, int height)
{
if(width <= 0 || height <= 0)
return;
m_width = width;
m_height = height;
m_matrix = new float*[height];
for(int i = 0; i < height; i++)
{
m_matrix[i] = new float[width];
for(int j = 0; j < height; j++)
{
m_matrix[i][j] = 0;
}
}
}
DynamicMatrix::~DynamicMatrix()
{
Destroy();
}
void DynamicMatrix::Destroy()
{
if (m_matrix)
{
for(int i = 0; i < m_height; i++)
{
delete [](m_matrix[i]);
}
m_matrix = NULL;
}
}
int DynamicMatrix::GetWidth()
{
return m_width;
}
int DynamicMatrix::GetHeight()
{
return m_height;
}
float DynamicMatrix::GetValue(int row, int col)
{
float val = 0;
if (row >= 0 && row < m_height && col >= 0 && col <= m_width)
{
val = m_matrix[row][col];
}
return val;
}
void DynamicMatrix::SetValue(int row, int col, float value)
{
if(row >= 0 && row < m_height && col >= 0 && col <= m_width)
{
m_matrix[row][col] = value;
}
}
void DynamicMatrix::LoadIdentity()
{
if(NULL == m_matrix)
{
return;
}
for(int i = 0; i < m_height; i++)
for(int j = 0; j < m_width; j++)
{
if(i == j)
{
m_matrix[i][j] = 1;
}
else
{
m_matrix[i][j] = 0;
}
}
}
void DynamicMatrix::LoadZeros()
{
if(NULL == m_matrix)
{
return;
}
for(int i = 0; i < m_height; i++)
for(int j = 0; j < m_width; j++)
m_matrix[i][j] = 0;
}
bool DynamicMatrix::Multiply(DynamicMatrix& inMatrix, int order)
{
if(!m_matrix)
return false;
int inHeight = inMatrix.GetHeight();
int inWidth = inMatrix.GetWidth();
int i = 0;
int j = 0;
int k = 0;
int m = 0;
float total = 0;
DynamicMatrix* result = NULL;
if(order == MATRIX_ORDER_APPEND)
{
//inMatrix on the left
if(m_width != inHeight)
return false;
DynamicMatrix result(inWidth, m_height);
for(i = 0; i < m_height; i++)
for(j = 0; j < inWidth; j++)
{
total = 0;
for(k = 0, m = 0; k < max(m_height, inHeight) && m < max(m_width, inWidth); k++, m++)
{
total = total + (inMatrix.GetValue(k, j) * m_matrix[i][m]);
}
result.SetValue(i, j, total);
}
// destroy self and recreate with a new dimension
Destroy();
Create(inWidth, m_height);
// assign result back to self
for(i = 0; i < inHeight; i++)
for(j = 0; j < m_width; j++)
m_matrix[i][j] = result.GetValue(i, j);
}
else
{
// inMatrix on the right
if(m_height != inWidth)
return false;
DynamicMatrix result(m_width, inHeight);
for(i = 0; i < inHeight; i++)
for(j = 0; j < m_width; j++)
{
total = 0;
for(k = 0, m = 0; k < max(inHeight, m_height) && m < max(inWidth, m_width); k++, m++)
{
total = total + ( (m_matrix[k][j]) * (inMatrix.GetValue(i, m)));
}
result.SetValue(i, j, total);
}
// destroy self and recreate with a new dimension
Destroy();
Create(m_width, inHeight);
// assign result back to self
for(i = 0; i < inHeight; i++)
for(j = 0; j < m_width; j++)
m_matrix[i][j] = result.GetValue(i, j);
}
return true;
}
bool DynamicMatrix::MultiplyNumber(float value)
{
if(!m_matrix)
return false;
for(int i = 0; i < m_height; i++)
for(int j = 0; j < m_width; j++)
{
float total = 0;
total = m_matrix[i][j] * value;
m_matrix[i][j] = total;
}
return true;
}
bool DynamicMatrix::Add(DynamicMatrix&inMatrix)
{
if(!m_matrix)
return false;
int inHeight = inMatrix.GetHeight();
int inWidth = inMatrix.GetWidth();
if(m_width != inWidth || m_height != inHeight)
return false;
for(int i = 0; i < m_height; i++)
for(int j = 0; j < m_width; j++)
{
float total = 0;
total = m_matrix[i][j] + inMatrix.GetValue(i, j);
m_matrix[i][j] = total;
}
return true;
}
///
float ColorMatrix::LUMINANCER = 0.3086;
float ColorMatrix::LUMINANCEG = 0.6094;
float ColorMatrix::LUMINANCEB = 0.0820;
ColorMatrix::ColorMatrix():DynamicMatrix(5, 5)
{
LoadIdentity();
}
void ColorMatrix::SetBrightnessMatrix(float value)
{
if (!m_matrix)
return;
m_matrix[0][4] = value;
m_matrix[1][4] = value;
m_matrix[2][4] = value;
}
void ColorMatrix::SetContrastMatrix(float value)
{
if(!m_matrix)
return;
float brightness= 0.5 * (127.0 - value);
value = value / 127.0;
m_matrix[0][0] = value;
m_matrix[1][1] = value;
m_matrix[2][2] = value;
m_matrix[0][4] = brightness;
m_matrix[1][4] = brightness;
m_matrix[2][4] = brightness;
}
void ColorMatrix::SetSaturationMatrix(float value)
{
if(!m_matrix)
return;
float subVal = 1.0 - value;
float mulVal= subVal * LUMINANCER;
m_matrix[0][0] = mulVal + value;
m_matrix[1][0] = mulVal;
m_matrix[2][0] = mulVal;
mulVal = subVal * LUMINANCEG;
m_matrix[0][1] = mulVal;
m_matrix[1][1] = mulVal + value;
m_matrix[2][1] = mulVal;
mulVal = subVal * LUMINANCEB;
m_matrix[0][2] = mulVal;
m_matrix[1][2] = mulVal;
m_matrix[2][2] = mulVal + value;
}
void ColorMatrix::SetHueMatrix(float angle)
{
if(!m_matrix)
return;
LoadIdentity();
DynamicMatrix baseMat(3, 3);
DynamicMatrix cosBaseMat(3, 3);
DynamicMatrix sinBaseMat(3, 3);
float cosValue = cos(angle);
float sinValue = sin(angle);
// slightly smaller luminance values from SVG
float lumR = 0.213;
float lumG = 0.715;
float lumB = 0.072;
baseMat.SetValue(0, 0, lumR);
baseMat.SetValue(1, 0, lumR);
baseMat.SetValue(2, 0, lumR);
baseMat.SetValue(0, 1, lumG);
baseMat.SetValue(1, 1, lumG);
baseMat.SetValue(2, 1, lumG);
baseMat.SetValue(0, 2, lumB);
baseMat.SetValue(1, 2, lumB);
baseMat.SetValue(2, 2, lumB);
cosBaseMat.SetValue(0, 0, (1 - lumR));
cosBaseMat.SetValue(1, 0, -lumR);
cosBaseMat.SetValue(2, 0, -lumR);
cosBaseMat.SetValue(0, 1, -lumG);
cosBaseMat.SetValue(1, 1, (1 - lumG));
cosBaseMat.SetValue(2, 1, -lumG);
cosBaseMat.SetValue(0, 2, -lumB);
cosBaseMat.SetValue(1, 2, -lumB);
cosBaseMat.SetValue(2, 2, (1 - lumB));
cosBaseMat.MultiplyNumber(cosValue);
sinBaseMat.SetValue(0, 0, -lumR);
sinBaseMat.SetValue(1, 0, 0.143); // not sure how this value is computed
sinBaseMat.SetValue(2, 0, -(1 - lumR));
sinBaseMat.SetValue(0, 1, -lumG);
sinBaseMat.SetValue(1, 1, 0.140); // not sure how this value is computed
sinBaseMat.SetValue(2, 1, lumG);
sinBaseMat.SetValue(0, 2, (1 - lumB));
sinBaseMat.SetValue(1, 2, -0.283); // not sure how this value is computed
sinBaseMat.SetValue(2, 2, lumB);
sinBaseMat.MultiplyNumber(sinValue);
baseMat.Add(cosBaseMat);
baseMat.Add(sinBaseMat);
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
m_matrix[i][j] = baseMat.GetValue(i, j);
}
float** ColorMatrix::GetFlatArray()
{
if(!m_matrix)
return NULL;
return m_matrix;
}
通过测试使用同样一张1024*768的图片,用这个方法使用的时间在40ms左右,效率相对来说比较高。但是效果和photoshop的同样的值调整出来的不一样,可能是经验值导致的,具体原因还不清楚。如果需要可以做一个工具让美术根据这个算法调整相应值。