RGB colours<\/a>)\n <\/p>\n\n \n Expanding it you can see it comprises a long list of integers. \n I made a diagram, which will hopefully help illustrate how the data is structured: \n <\/p>\n\n
\n\n
\n By mixing red, green and blue, we get the colour of the pixel. The fourth tells us the alpha value (opacity). \n <\/p>\n\n
\n We can loop through the array of these numbers and use these RGBA values to manipulate the look of the image. \n <\/p>\n\n
\n As we only care about the data property of the object, I am saving it to the variable imgData<\/code>.\n <\/p>\n\n \n Then I am adding a for<\/code> loop. The iterator i<\/code> will start at 0, so the very first array element. \n The loop should keep on going until the end of the array. \n Whenever one of the iterations finishes, I want to increment i<\/code> by 4. \n The reason for this is that each pixel is represented by 4 values within the array as you could see on the diagram. \n <\/p>\n\n \n Within the loop, I am saving each colour value into its own variable. \n Red will be the first value within the iteration. Green, we can get by adding 1 to i<\/code>, blue by adding 2. \n You can also access alpha by adding 3, but we don't need alpha for this project. \n <\/p>\n\n \n Gray shades are made up of equal values of each colour. \n So, to turn an image into a grayscale image, all we need to do is add the red, \n green and blue values together and divide it by 3 to get their average. \n <\/p>\n\n
\n Once we have the gray value for the pixel we are iterating over, \n we can edit its colour by assigning the result back to the red, \n green and blue array elements. \n We can use the same indexes for this as we used to initially get the colour values. \n <\/p>\n\n
\n Finally, we need to draw the edited image onto the canvas.\n We can do this with context.putImageData(image, 0, 0);<\/code> \n and passing the same arguments we did to drawImage in the image event listener at the beginning. \n <\/p>\n\n \n To test it, ensure you attach an event listener to the button with the ID grayscale<\/code>: \n\n \n grayscale.addEventListener('click', applyGrayscale);\n <\/code>\n <\/p>\n\n The Monotone Filter<\/h3>\n \n \n \n const applyMonotone = () => {\n const image = context.getImageData(0, 0, imageWidth, imageHeight);\n const imgData = image.data; \n \n for(let i = 0; i < imgData.length; i += 4) {\n const red = imgData[i]; \n const green = imgData[i + 1]; \n const blue = imgData[i + 2]; \n \n const grayValue = (red + green + blue) \/ 3;\n imgData[i] = grayValue - 40;\n imgData[i + 1] = grayValue - 40;\n imgData[i + 2] = grayValue + 80;\n }\n \n context.putImageData(image, 0, 0); \n }\n <\/code>\n <\/pre>\n\n \n As you can see the functionality for creating the monotone effect is very similar to the grayscale one. \n The only difference is in the values that get assigned back to the image. \n (We will refactor the code towards the end of the tutorial so there's no unnecessary repetition).\n <\/p>\n\n
\n To make one colour dominant, you just need to increase its value. \n For example, in this case, we are making blue the dominant colour, \n so I'm increasing it by 80, and reducing red and green by 40.\n <\/p>\n\n
\n Reducing the value of the other colours has the benefit that the brightness of the image remains the same. \n The higher the value of each colour, the brighter the image. \n <\/p>\n\n
\n To make green dominant, you could assign grayValue + 80<\/code> to it and reduce the other ones by 40. \n To make magenta dominant, you could increase the values of both red and blue by 40, while decreasing green by 80. \n I recommend experimenting around and see what you can come up with. \n <\/p>\n\n \n Again, make sure to attach an event listener to the appropriate button:
\n \n monotone.addEventListener('click', applyMonotone);\n <\/code>\n <\/p>\n\n The Duotone Filter<\/h3>\n\n \n \n const applyDuotone = () => {\n const image = context.getImageData(0, 0, imageWidth, imageHeight);\n const imgData = image.data; \n \n for(let i = 0; i < imgData.length; i += 4) {\n const red = imgData[i]; \n const green = imgData[i + 1]; \n const blue = imgData[i + 2]; \n \n const grayValue = (red + green + blue) \/ 3;\n const diff = Math.round((128\/100) * grayValue); \n imgData[i] = grayValue + diff;\n imgData[i + 1] = grayValue;\n imgData[i + 2] = 255 - diff;\n }\n \n context.putImageData(image, 0, 0); \n }\n\n duotone.addEventListener('click', applyDuotone);\n <\/code>\n <\/pre>\n\n \n To create the duotone effect, I came up with the algorithm myself after experimenting around for ~2 hours. \n If you search the internet, you might find better implementations, \n so I recommend looking around. Or you could also come up with your own algorithm. \ud83e\udd13\n <\/p>\n\n
\n I will try to explain the logic behind it as much as possible though:\n <\/p>\n \n
\n Whenever we calculate the gray value by getting the average of red, green and blue, we get a value between 0 and 255. \n 0 is the colour black, 255 is white, and anything in between is a different shade of gray. \n The higher the value the lighter the gray.\n <\/p>\n\n
\n The middle point between 0 and 255 is 128. \n To apply a duotone, I want to check how far away the grayValue is from this point. \n For this, I can use percentage. \n <\/p>\n\n
\n As 128 is the reference point from which I want to calculate the difference, \n 128 becomes the 100% mark. \n Then with the formula 128 divided by 100 and multiplied by the grayValue, \n I can figure out how much percent grayValue is of 128: \n <\/p>\n\n
\n const diff = Math.round((128\/100) * grayValue)<\/code>. \n <\/p>\n\n \n Often, percentage calculations return decimal numbers. \n That's why I'm using Math.random()<\/code> to round the number up or down.\n <\/p>\n\n \n For the red value, I am setting grayValue + diff<\/code>. \n Green will be set to the regular grayValue<\/code> without any alterations. \n And blue will become 255 - diff<\/code>. \n (You can also make the filter have more of a magenta hue by setting blue to simply be 255). \n <\/p>\n\n Refactoring<\/h3>\n\n
\n The time has come to remove code duplication.\n I'm combining all the common code into a new function called \n applyFilter<\/code>. \n Additionally, I am also adding a filters<\/code> object, \n which holds 3 methods. These will generate an array of values that represent the RGB colours. \n <\/p>\n\n \n \n const applyFilter = (filterType) => {\n const image = context.getImageData(0, 0, imageWidth, imageHeight);\n const imgData = image.data; \n \n for(let i = 0; i < imgData.length; i += 4) {\n const red = imgData[i]; \n const green = imgData[i + 1]; \n const blue = imgData[i + 2]; \n \n const grayValue = (red + green + blue) \/ 3;\n const data = filters[filterType](grayValue);\n imgData[i] = data[0];\n imgData[i + 1] = data[1];\n imgData[i + 2] = data[2];\n }\n \n context.putImageData(image, 0, 0); \n }\n\n const filters = {\n applyGrayscale: (gray) => {\n return [gray, gray, gray];\n }, \n applyMonotone: (gray) => {\n return [gray - 40, gray - 40, gray + 80];\n },\n applyDuotone: (gray) => {\n const diff = Math.round((128\/100) * gray); \n return [gray + diff, gray, 255 - diff];\n },\n }\n <\/code>\n <\/pre>\n\n \n The major changes to note are: \n <\/p>\n\n
\n The applyFilter<\/code> function takes one argument: \n filterType<\/code>. \n The filterType<\/code> defines which filter should be applied \n and can have the value applyGrayscale<\/code>, \n applyMonotone<\/code> or \n applyDuotone<\/code>.\n <\/p>\n\n \n Based on what string is passed as filterType<\/code>, \n we dynamically invoke the method with the same name from the filters<\/code>object\n and pass grayValue<\/code> to it: \n <\/p>\n\n \n const data = filters[filterType](grayValue);\n <\/code>\n\n \n The methods within filters<\/code> then calculate the \n RGB values and return them as an array. These values can then be assign back to the image within \n the applyFilter<\/code> function: \n <\/p>\n\n \n \n imgData[i] = data[0];\n imgData[i + 1] = data[1];\n imgData[i + 2] = data[2];\n <\/code>\n <\/pre>\n\n \n Finally, we also need to update the click event handlers:\n <\/p>\n\n
\n \n grayscale.addEventListener('click', () => applyFilter('applyGrayscale'));\n monotone.addEventListener('click', () => applyFilter('applyMonotone'));\n duotone.addEventListener('click', () => applyFilter('applyDuotone'));\n <\/code>\n <\/pre><\/p>\n\n