main
Isaiah Odhner 2021-02-10 14:53:57 -05:00
parent de6364fef2
commit 7a8f314be1
3 changed files with 48 additions and 55 deletions

View File

@ -73,16 +73,16 @@ function show_edit_colors_window($swatch_to_edit, color_selection_slot_to_edit)
let old_rgba = get_rgba_from_color(palette[swatch_index]);
const new_rgba = get_rgba_from_color(color);
const other_rgba = get_rgba_from_color(palette[14 - swatch_index]);
const was_monochrome = detect_monochrome(ctx);
const was_monochrome_selection = (selection && selection.canvas) ? detect_monochrome(selection.canvas.ctx) : was_monochrome;
const main_monochrome_info = detect_monochrome(ctx);
const selection_monochrome_info = (selection && selection.canvas) ? detect_monochrome(selection.canvas.ctx) : main_monochrome_info;
const selection_matches_main_canvas_colors =
was_monochrome_selection &&
was_monochrome_selection.every((rgba)=>
was_monochrome.map(rgba=> rgba.toString()).includes(rgba.toString())
selection_monochrome_info.isMonochrome &&
selection_monochrome_info.presentNonTransparentRGBAs.every((rgba)=>
main_monochrome_info.presentNonTransparentRGBAs.map(rgba=> rgba.toString()).includes(rgba.toString())
);
if (
was_monochrome &&
was_monochrome_selection &&
main_monochrome_info.isMonochrome &&
selection_monochrome_info.isMonochrome &&
selection_matches_main_canvas_colors
) {
const recolor = (ctx, present_rgbas)=> {
@ -112,9 +112,9 @@ function show_edit_colors_window($swatch_to_edit, color_selection_slot_to_edit)
name: "Recolor",
icon: get_help_folder_icon("p_monochrome_undo.png"),
}, ()=> {
recolor(ctx, was_monochrome);
recolor(ctx, main_monochrome_info.presentNonTransparentRGBAs);
if (selection && selection.canvas) {
recolor(selection.canvas.ctx, was_monochrome_selection);
recolor(selection.canvas.ctx, selection_monochrome_info.presentNonTransparentRGBAs);
// I feel like this shouldn't be necessary, if I'm not changing the size, but it makes it work:
selection.replace_source_canvas(selection.canvas);
}

View File

@ -1907,8 +1907,9 @@ function image_invert_colors(){
name: localize("Invert Colors"),
icon: get_help_folder_icon("p_invert.png"),
}, (original_canvas, original_ctx, new_canvas, new_ctx) => {
if (monochrome && detect_monochrome(original_ctx)) {
invert_monochrome(original_ctx, new_ctx);
const monochrome_info = monochrome && detect_monochrome(original_ctx);
if (monochrome && monochrome_info.isMonochrome) {
invert_monochrome(original_ctx, new_ctx, monochrome_info);
} else {
invert_rgb(original_ctx, new_ctx);
}
@ -2024,19 +2025,29 @@ function detect_monochrome(ctx) {
// Note: values in pixelArray may be different on big endian vs little endian machines.
// Use id.data, which is guaranteed to be in RGBA order, for getting color information.
// Only use the Uint32Array for comparing pixel equality (faster than comparing each color component).
const colorValues = [];
const colorUint32s = [];
const colorRGBAs = [];
let anyTransparency = false;
for(let i=0, len=pixelArray.length; i<len; i+=1){
if (!colorValues.includes(pixelArray[i]) && id.data[i*4+3] !== 0) {
if (colorValues.length < 2) {
colorValues.push(pixelArray[i]);
colorRGBAs.push(id.data.slice(i*4, (i+1)*4));
} else {
return false;
if (id.data[i*4+3]) {
if (!colorUint32s.includes(pixelArray[i])) {
if (colorUint32s.length < 2) {
colorUint32s.push(pixelArray[i]);
colorRGBAs.push(id.data.slice(i*4, (i+1)*4));
} else {
return {isMonochrome: false};
}
}
} else {
anyTransparency = true;
}
}
return colorRGBAs;
return {
isMonochrome: true,
presentNonTransparentRGBAs: colorRGBAs,
presentNonTransparentUint32s: colorUint32s,
monochromeWithTransparency: anyTransparency,
};
}
function make_monochrome_pattern(lightness, rgba1=[0, 0, 0, 255], rgba2=[255, 255, 255, 255]){
@ -2269,7 +2280,7 @@ function image_attributes(){
const unit = $units.find(":checked").val();
const was_monochrome = monochrome;
const image_was_actually_monochrome = detect_monochrome(ctx);
const monochrome_info = detect_monochrome(ctx);
image_attributes.unit = unit;
transparency = (transparency_option == "transparent");
@ -2277,8 +2288,8 @@ function image_attributes(){
if(monochrome != was_monochrome){
if(monochrome){
if(image_was_actually_monochrome && image_was_actually_monochrome.length === 2) {
palette = make_monochrome_palette(image_was_actually_monochrome[0], image_was_actually_monochrome[1]);
if(monochrome_info.isMonochrome && monochrome_info.presentNonTransparentRGBAs.length === 2) {
palette = make_monochrome_palette(...monochrome_info.presentNonTransparentRGBAs);
}else{
palette = monochrome_palette;
}
@ -2307,7 +2318,7 @@ function image_attributes(){
// We only want to show this dialog if it would also change the palette (above), never leave you on an outdated palette.
// (And it's nice to be able to change other options without worrying about it trying to convert the document to monochrome.)
if(monochrome != was_monochrome){
if (monochrome && !image_was_actually_monochrome) {
if (monochrome && !monochrome_info.isMonochrome) {
show_convert_to_black_and_white();
}
}

View File

@ -748,38 +748,18 @@ function invert_rgb(source_ctx, dest_ctx=source_ctx) {
dest_ctx.putImageData(image_data, 0, 0);
}
function invert_monochrome(source_ctx, dest_ctx=source_ctx) {
function invert_monochrome(source_ctx, dest_ctx=source_ctx, monochrome_info=detect_monochrome(source_ctx)) {
const image_data = source_ctx.getImageData(0, 0, source_ctx.canvas.width, source_ctx.canvas.height);
// Note: values in pixel_array may be different on big endian vs little endian machines.
// Only rely on equality of values within the array.
// pixel_array is a performance optimization, to access whole pixels at a time instead of individual color channels.
// Note: code copied from detect_monochrome; @TODO: make detect_monochrome return an object with various info
const pixel_array = new Uint32Array(image_data.data.buffer);
const color_values = [];
const color_rgbas = [];
let any_transparency = false;
for(let i=0, len=pixel_array.length; i<len; i+=1){
if (image_data.data[i*4+3]) {
if (!color_values.includes(pixel_array[i])) {
if (color_values.length < 2) {
color_values.push(pixel_array[i]);
color_rgbas.push(image_data.data.slice(i*4, (i+1)*4));
} else {
console.error("Not monochrome!");
dest_ctx.putImageData(image_data, 0, 0);
return;
}
}
} else {
any_transparency = true;
}
}
if (color_values.length === 0) {
if (monochrome_info.presentNonTransparentUint32s.length === 0) {
// Fully transparent.
// No change, and no need to copy the image to dest canvas to represent that lack of a change.
return;
}
if (color_values.length === 1) {
if (monochrome_info.presentNonTransparentUint32s.length === 1) {
// Only one non-transparent color present in the image.
// Can't use just the information of what colors are in the canvas to invert, need to look at the palette.
// We could've done this in a unified way, but whatever!
@ -788,28 +768,30 @@ function invert_monochrome(source_ctx, dest_ctx=source_ctx) {
const color_1 = palette[0];
const color_2 = palette[14] || palette[1];
const color_1_rgba = get_rgba_from_color(color_1);
const present_rgba = monochrome_info.presentNonTransparentRGBAs[0];
if (
color_rgbas[0][0] === color_1_rgba[0] &&
color_rgbas[0][1] === color_1_rgba[1] &&
color_rgbas[0][2] === color_1_rgba[2] &&
color_rgbas[0][3] === color_1_rgba[3]
present_rgba[0] === color_1_rgba[0] &&
present_rgba[1] === color_1_rgba[1] &&
present_rgba[2] === color_1_rgba[2] &&
present_rgba[3] === color_1_rgba[3]
) {
dest_ctx.fillStyle = color_2;
} else {
dest_ctx.fillStyle = color_1;
}
if (any_transparency) {
if (monochrome_info.any_transparency) {
dest_ctx.putImageData(image_data, 0, 0);
dest_ctx.globalCompositeOperation = "source-in";
}
dest_ctx.fillRect(0, 0, source_ctx.canvas.width, source_ctx.canvas.height);
return;
}
const [uint32_a, uint32_b] = monochrome_info.presentNonTransparentUint32s;
for(let i=0, len=pixel_array.length; i<len; i+=1){
if (pixel_array[i] === color_values[0]) {
pixel_array[i] = color_values[1];
} else if (pixel_array[i] === color_values[1]) {
pixel_array[i] = color_values[0];
if (pixel_array[i] === uint32_a) {
pixel_array[i] = uint32_b;
} else if (pixel_array[i] === uint32_b) {
pixel_array[i] = uint32_a;
}
}
dest_ctx.putImageData(image_data, 0, 0);