The problem background
I recently came across a scenario in my work where the image and copyright information on the login page are configurable and the layout is full of images, the copyright information is located under the window, and the layout is shown as below, like this. The text was white in the design draft, but I didn’t think much about it at that time, so I just wrote the color dead. After changing a white background picture, I couldn’t see the copyright information at all. Therefore, I did some optimization, judging its YIQ value according to the average color value in some areas of the picture. If you are interested in YIQ value, you can check it. I directly explain it as color system here, which is to judge whether the color is black or white. Determine whether the text should be black or white based on which will have the highest contrast to provide the best readability.
The implementation process
Let me describe the entire process of solving this problem.
First of all, we need to draw the picture with the help of the Canvas in JavaScript, and calculate the average color value. Here, we will use two apis: drawImage and getImageData. The code implementation is as follows:
First, explain the meaning of the data parameter
- ImgSrc: To calculate the address of the image
- XMultiple Optional: Take the X-axis distance of the intercept point, and take the proportional coefficient to the image width. Default is 0
- YMultiple Optional: Select the y pivot distance of the intercept point and take the proportional coefficient with the height of the image. The default is 0
- WMultiple Optional: Scale to capture (based on image width). Default: 1
- HMultiple Optional: Proportion (based on image height) to capture. Default: 1
export const getImgContrast = async (
data: GetImgColorParams
): Promise<number[] > = > {const {
imgSrc,
xMultiple = 0,
yMultiple = 0,
wMultiple = 1,
hMultiple = 1
} = data
const multipleArr = [xMultiple, yMultiple, wMultiple, hMultiple]
const isVerify = multipleArr.every(item= > item >= 0 && item <= 1)
if(! isVerify) {throw new Error('Please enter a valid scaling factor, i.e. a number greater than 0 and less than 1')}const myCanvas = document.createElement('canvas')
// This is an asynchronous method that encapsulates the creation of image tags
// const bgImg = document.createElement("img");
// bgImg.src = src;
const bgImg = await createImage(imgSrc)
const iHeight = bgImg.height
const iWidth = bgImg.width
const canvasWidth = iWidth * wMultiple
const canvasHeight = iHeight * hMultiple
const canvasSize = canvasWidth * canvasHeight
myCanvas.width = iWidth * wMultiple
myCanvas.height = iHeight * hMultiple
const ctx = myCanvas.getContext('2d')
if (ctx) {
ctx.drawImage(
bgImg,
iWidth * xMultiple,
iHeight * yMultiple,
canvasWidth,
canvasHeight,
0.0,
canvasWidth,
canvasHeight
)
// Get the pixel data of the canvas image
const data = ctx.getImageData(0.0, canvasWidth, canvasHeight).data
let r = 0
let g = 0
let b = 0
// Take the average of all pixels
for (let col = 0; col < canvasWidth; col++) {
for (let row = 0; row < canvasHeight; row++) {
r += data[canvasWidth * row + col * 4]
g += data[canvasWidth * row + col * 4 + 1]
b += data[canvasWidth * row + col * 4 + 2]}}// take the average value
r = Math.round(r / canvasSize)
g = Math.round(g / canvasSize)
b = Math.round(b / canvasSize)
return [r, g, b]
}
return [0.0.0]}Copy the code
Draw pictures
If you want to calculate the average color value of the whole image, you don’t have to do that, you just pass in the image link using the drawImage method, but I’m just taking parts and encapsulating the method, so it looks like you’re going to do a lot more, just look at the main part.
Calculate the average
ingetImageData
Method, you just create the tag and go tocanvas
The process of drawing a picture in. After this process is completed, we pass throughgetImageData
Method to getcanvas
An array of all the pixels of an image. It looks something like this,imageData.data
Is a one-dimensional array containing data in RGBA order, represented as integers from 0 to 255 inclusive.
Once you have the average color of the part you want, use the W3C YIQ algorithm to determine whether the color is black or white. So we know what color the final text should be.
// https://www.w3.org/TR/AERT/#color-contrast
export const getContrastYIQ = (r: number, g: number, b: number) = > {
const yiq = (r * 299 + g * 587 + b * 114) / 1000
return yiq >= 128 ? 'black' : 'white'
}
Copy the code
At the end
I’ve wrapped the entire method and posted it to NPM. With the reference package you can use the react-hooks method to call calculations, as well as directly to call methods that not only calculate images, but also hexadecimal colors for text. Specific use can see the documentation. Git repository address: github.com/chanceyliu/…