RGBA格式圖像中HSBC(色相、飽和度、明度、對比度)調整

最近項目中有一個需求:

美術給定一張圖片,通過配置文件,在不同建築等級時使用同一張圖片,但是在不同等級時通過程序不斷調整hsbc(photoshop中也可以調整)顯示出不同的效果。通過搜索,有兩種方法解決:


首先介紹下HSL和HSV、HSB:

HSL 和 HSV(也叫HSB)是對RGB 色彩空間中點的兩種有關係的表示,它們嘗試描述比 RGB 更準確的感知顏色聯繫,並仍保持在計算上簡單。

H指hue(色相)、S指saturation(飽和度)、L指lightness(亮度)、V指value(色調)、B指brightness(明度

  • 色相(H)是色彩的基本屬性,就是平常所說的顏色名稱,如紅色黃色等。
  • 飽和度(S)是指色彩的純度,越高色彩越純,低則逐漸變灰,取0-100%的數值。
  • 明度(V),亮度(B),取0-100%。

方法一:
因此要調整RGBA格式的的hsbc,可以先將像素的RGB值轉換成HSB格式,然後調整HSB再轉換成RGB值就可以了,本人用這種方法處理一張1024*768的圖片在pc上執行是時間大約是200ms,在每秒30幀的遊戲中這樣的速度顯然是不行的,如果不考慮效率,這種方法也是非常方便的,建議不用擔心效率的時候使用該方法。

方法二:
有沒有不用轉換格式直接對RGB做特殊處理呢,答案是肯定的:參照AS中ColorMatrixFilter色彩矩陣濾鏡

這裏的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%,同時保留顏色通道的值。

所以整個過程中最重要的是求出這個a數組,請參考以下C++的實現,稍微看得懂C++的就可以將其轉換成C代碼或者其他語言的~

類說明
AdjustColor:傳入色相、飽和度、明度、對比度值,得出結果的類
ColorMatrix:根據色相、飽和度、明度、對比度值的值分別調整數組內容的類

調用方式可以參考AdjustColor.h中的C接口void ColorAdjust(ColorHSB_T& hsb, void* SrcData, int width, int height)

下面是用到的幾個文件:
AdjustColor.h
#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;
}

ColorMatrix.h
#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

ColorMatrix.cpp
#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的同樣的值調整出來的不一樣,可能是經驗值導致的,具體原因還不清楚。如果需要可以做一個工具讓美術根據這個算法調整相應值。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章