From 4e0376868cf877626fb73abcca8cab14a2f4f7e6 Mon Sep 17 00:00:00 2001 From: Szymon Wiszczuk Date: Thu, 20 Apr 2023 17:47:09 +0200 Subject: [PATCH] feat: add harmonic and geometric mean --- README.md | 6 ++--- src/fixImage.ts | 11 +++++++++ src/utils.ts | 65 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5fcdef5..ae09f5e 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,9 @@ fixImage(png: MinimumData, options: FixOptions) Below you can find all the existing strategies and what they do underneath the hood. PRs are open for more! -- Strategies.MAJORITY - take the color that occurs the most often in the block -- Strategies.AVERAGE - take the average of colors in the blocks -- Strategies.ALG(05|10|20|30|40|50|60|70|80|90) - a mix; if a color is making up above X%, then take it, otherwise take the average +- `Strategies.MAJORITY` - take the color that occurs the most often in the block +- `Strategies.AVERAGE` - take the average of colors in the blocks +- `Strategies.ALG(05|10|20|30|40|50|60|70|80|90)` - a mix; if a color is making up above X%, then take it, otherwise take the average ### Examples diff --git a/src/fixImage.ts b/src/fixImage.ts index c5ef1d8..2449d72 100644 --- a/src/fixImage.ts +++ b/src/fixImage.ts @@ -5,6 +5,8 @@ import { Pixel, Strategies, getAverageOfColors, + getGeometricMeanOfColors, + getHarmonicMeanOfColors, getMajorityColor, } from "./utils"; @@ -84,7 +86,16 @@ export const fixImage = ( blocks[bI] = new Array(block.length).fill(color); break; + case Strategies.HARMONIC: + const harmonic = getHarmonicMeanOfColors(block); + blocks[bI] = new Array(block.length).fill(harmonic); + break; + case Strategies.GEOMETRIC: + const geometric = getGeometricMeanOfColors(block); + + blocks[bI] = new Array(block.length).fill(geometric); + break; // get all the alg cases default: const coverage: number = occurences / (outPixWidth * outPixHeight); diff --git a/src/utils.ts b/src/utils.ts index 8942eeb..0af97fd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -17,6 +17,10 @@ export const Strategies = Object.freeze({ MAJORITY: "majority", // just take the mean out of colors in the current block AVERAGE: "average", + // harmonic mean + HARMONIC: "harmonic", + // geometric mean + GEOMETRIC: "geometric", // take the color only if it is present for over X% of the image, otherwise take average ALG05: 0.05, ALG10: 0.1, @@ -41,22 +45,30 @@ export interface FixOptions { } const accumulateColors = (pixels: Array, i: number) => + pixels.map((e) => e[i]); + +const sumColors = (pixels: Array, i: number) => pixels.reduce((acc, item) => { return acc + item[i]; }, 0); +const multiplyColors = (pixels: Array, i: number) => + pixels.reduce((acc, item) => { + return acc * item[i]; + }, 1); + export const getAverageOfColors = (pixels: Array): Pixel => { - const redAccumulated = accumulateColors(pixels, 0); + const redAccumulated = sumColors(pixels, 0); const red = redAccumulated / pixels.length; - const greenAccumulated = accumulateColors(pixels, 1); + const greenAccumulated = sumColors(pixels, 1); const green = greenAccumulated / pixels.length; - const blueAccumulated = accumulateColors(pixels, 2); + const blueAccumulated = sumColors(pixels, 2); const blue = blueAccumulated / pixels.length; // TODO: Rethink whether alpha should be in this - const alphaAccumulated = accumulateColors(pixels, 3); + const alphaAccumulated = sumColors(pixels, 3); const alpha = alphaAccumulated / pixels.length; return [ @@ -67,6 +79,51 @@ export const getAverageOfColors = (pixels: Array): Pixel => { ]; }; +const getHarmonicMean = (numsToAvg: Array) => { + const sumOfNegativePowers = numsToAvg + .map((num) => 1 / num) + .reduce((acc, num) => acc + num, 0); + return Math.round(numsToAvg.length / sumOfNegativePowers); +}; + +export const getHarmonicMeanOfColors = (pixels: Array) => { + const redAccumulated = accumulateColors(pixels, 0); + const red = getHarmonicMean(redAccumulated); + + const greenAccumulated = accumulateColors(pixels, 1); + const green = getHarmonicMean(greenAccumulated); + + const blueAccumulated = accumulateColors(pixels, 2); + const blue = getHarmonicMean(blueAccumulated); + + const alphaAccumulated = accumulateColors(pixels, 3); + const alpha = getHarmonicMean(alphaAccumulated); + + return [red, green, blue, alpha]; +}; + +const getGeometricMean = (numsToAvg: Array) => { + const product = numsToAvg.reduce((acc, item) => acc * item, 1); + + return Math.round(Math.pow(product, 1 / numsToAvg.length)); +}; + +export const getGeometricMeanOfColors = (pixels: Array) => { + const redAccumulated = accumulateColors(pixels, 0); + const red = getGeometricMean(redAccumulated); + + const greenAccumulated = accumulateColors(pixels, 1); + const green = getGeometricMean(greenAccumulated); + + const blueAccumulated = accumulateColors(pixels, 2); + const blue = getGeometricMean(blueAccumulated); + + const alphaAccumulated = accumulateColors(pixels, 3); + const alpha = getGeometricMean(alphaAccumulated); + + return [red, green, blue, alpha]; +}; + interface MajorityColorData { color: Pixel; occurences: number; // n of occurences