Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | 6x 20x 20x 20x 20x 20x 20x 20x 20x 20x 20x 20x 20x 20x 20x 20x 20x 2644x 528800x 373165x 155635x 155635x 47240x 6437x 149198x 149198x 149198x 149198x 149198x 149198x 149198x 149198x 149198x 149198x 149198x 149198x 20x 1054478x 1054478x 1054478x 20x 905218x 20x 1x | import { PixelBlob } from "./pixel-blob";
/**
* For each possible color, this counts how many pixels in the source image match that color.
* If signifigantBits is less than 8, each channel (eg: red, green, blue) in each color is reduced to fit in significantBits. So for the default value of 5 significantBits colors are reduced from 8 bits per channel (0-255) to 5 (0-31). Colors that were previously distinct get combined together.
* If the image source has more than 2^32 pixels (eg: a square image 65536x65536 in size) of the same color this code will break.
*
* @public
*/
export class Histogram {
/**
* @param source - the source pixel data.
* @param significantBits - The memory needed for the histogram increases dramatically if significantBits is increased. It needs a buffer which is 4*2^(3*significantBits) in size. EG: for 5 significant bits the histogram is 128K while for 8 it is 64 megs.
* @param pixelSkipping - CPU time increases linearly as pixelSkipping is reduced.
* @param isHistogramPixelValid - isHistogramPixelValid is an optional predicate which can screen out unwanted pixels from the source data. EG: ignoring transparent pixels.
*/
constructor(
source: PixelBlob,
significantBits: number = 5,
pixelSkipping: number = 5,
isHistogramPixelValid: ((pixel: number[]) => boolean) | null = null
) {
Iif (significantBits < 1 || significantBits > 8) {
throw new Error("significantBits must be in the range [1,8]");
}
Iif (pixelSkipping < 0) {
throw new Error("pixelSkipping must be >= 0");
}
this.significantBits = significantBits;
const sigShift: number = 8 - this.significantBits;
this.minRed = 255 >>> sigShift;
this.maxRed = 0;
this.minGreen = 255 >>> sigShift;
this.maxGreen = 0;
this.minBlue = 255 >>> sigShift;
this.maxBlue = 0;
const histoSize: number = 1 << (significantBits * 3);
this.data = new Uint32Array(histoSize);
this.data.fill(0);
this.total = 0;
let pixelIndex: number = 0;
for (let y: number = 0; y < source.height; y++) {
for (let x: number = 0; x < source.width; x++) {
if (pixelSkipping > 0 && pixelIndex++ % pixelSkipping !== 0) {
continue;
}
const rgba: number[] = source.getPixelRGBA(x, y);
if (isHistogramPixelValid !== null) {
if (!isHistogramPixelValid(rgba)) {
continue;
}
}
// Shift the pixel data into the range determined by significantBits
// after checking minAlpha the alpha data is no longer needed
rgba[0] = rgba[0] >>> sigShift;
rgba[1] = rgba[1] >>> sigShift;
rgba[2] = rgba[2] >>> sigShift;
this.minRed = Math.min(rgba[0], this.minRed);
this.maxRed = Math.max(rgba[0], this.maxRed);
this.minGreen = Math.min(rgba[1], this.minGreen);
this.maxGreen = Math.max(rgba[1], this.maxGreen);
this.minBlue = Math.min(rgba[2], this.minBlue);
this.maxBlue = Math.max(rgba[2], this.maxBlue);
const histoIndex: number = this.getHistogramIndex(
rgba[0],
rgba[1],
rgba[2]
);
this.data[histoIndex] += 1;
this.total++;
}
}
}
public readonly data: Uint32Array;
public readonly significantBits: number;
public readonly total: number;
public readonly minRed: number;
public readonly maxRed: number;
public readonly minGreen: number;
public readonly maxGreen: number;
public readonly minBlue: number;
public readonly maxBlue: number;
public getHistogramIndex = (r: number, g: number, b: number): number => {
const index: number =
(r << (2 * this.significantBits)) + (g << this.significantBits) + b;
Iif (index >= this.data.length) {
throw new Error("RGB value is outside the bounds of the histogram");
}
return index;
};
public getHistogramValue = (r: number, g: number, b: number): number => {
return this.data[this.getHistogramIndex(r, g, b)];
};
public setHistogramValue = (value: number, r: number, g: number, b: number): void => {
this.data[this.getHistogramIndex(r, g, b)] = value;
};
}
|