diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 27e544957..0f59633cb 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -37,7 +37,11 @@ import { Util, warn, } from "../shared/util.js"; -import { PDFDateString, setLayerDimensions } from "./display_utils.js"; +import { + changeLightness, + PDFDateString, + setLayerDimensions, +} from "./display_utils.js"; import { AnnotationStorage } from "./annotation_storage.js"; import { ColorConverters } from "../shared/scripting_utils.js"; import { DOMSVGFactory } from "./svg_factory.js"; @@ -232,46 +236,13 @@ class AnnotationElement { const opacity = this.data.opacity ?? 1; const oppositeOpacity = 255 * (1 - opacity); - return this.#changeLightness( + return changeLightness( Math.min(r + oppositeOpacity, 255), Math.min(g + oppositeOpacity, 255), Math.min(b + oppositeOpacity, 255) ); } - #changeLightness(r, g, b) { - r /= 255; - g /= 255; - b /= 255; - - const max = Math.max(r, g, b); - const min = Math.min(r, g, b); - const l = (max + min) / 2; - const newL = (((1 + Math.sqrt(l)) / 2) * 100).toFixed(2); - - if (max === min) { - // gray - return `hsl(0, 0%, ${newL}%)`; - } - - const d = max - min; - - // hue (branch on max only; avoids mod) - let h; - if (max === r) { - h = (g - b) / d + (g < b ? 6 : 0); - } else if (max === g) { - h = (b - r) / d + 2; - } else { - // max === b - h = (r - g) / d + 4; - } - h = (h * 60).toFixed(2); - const s = ((d / (1 - Math.abs(2 * l - 1))) * 100).toFixed(2); - - return `hsl(${h}, ${s}%, ${newL}%)`; - } - _normalizePoint(point) { const { page: { view }, diff --git a/src/display/display_utils.js b/src/display/display_utils.js index 1db092a9d..17ec261da 100644 --- a/src/display/display_utils.js +++ b/src/display/display_utils.js @@ -770,7 +770,41 @@ const SupportedImageMimeTypes = [ "image/x-icon", ]; +function changeLightness(r, g, b, lumCallback = l => (1 + Math.sqrt(l)) / 2) { + r /= 255; + g /= 255; + b /= 255; + + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + const l = (max + min) / 2; + const newL = (lumCallback(l) * 100).toFixed(2); + + if (max === min) { + // gray + return `hsl(0, 0%, ${newL}%)`; + } + + const d = max - min; + + // hue (branch on max only; avoids mod) + let h; + if (max === r) { + h = (g - b) / d + (g < b ? 6 : 0); + } else if (max === g) { + h = (b - r) / d + 2; + } else { + // max === b + h = (r - g) / d + 4; + } + h = (h * 60).toFixed(2); + const s = ((d / (1 - Math.abs(2 * l - 1))) * 100).toFixed(2); + + return `hsl(${h}, ${s}%, ${newL}%)`; +} + export { + changeLightness, deprecated, fetchData, getColorValues, diff --git a/src/pdf.js b/src/pdf.js index d5cc9aff9..7b6954eb6 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -52,6 +52,7 @@ import { version, } from "./display/api.js"; import { + changeLightness, fetchData, getFilenameFromUrl, getPdfFilenameFromUrl, @@ -98,6 +99,7 @@ globalThis.pdfjsLib = { AnnotationMode, AnnotationType, build, + changeLightness, ColorPicker, createValidAbsoluteUrl, DOMSVGFactory, @@ -153,6 +155,7 @@ export { AnnotationMode, AnnotationType, build, + changeLightness, ColorPicker, createValidAbsoluteUrl, DOMSVGFactory, diff --git a/test/unit/display_utils_spec.js b/test/unit/display_utils_spec.js index 7369021e5..34975e554 100644 --- a/test/unit/display_utils_spec.js +++ b/test/unit/display_utils_spec.js @@ -14,12 +14,14 @@ */ import { + changeLightness, getFilenameFromUrl, getPdfFilenameFromUrl, + getRGB, isValidFetchUrl, PDFDateString, } from "../../src/display/display_utils.js"; -import { toBase64Util } from "../../src/shared/util.js"; +import { isNodeJS, toBase64Util } from "../../src/shared/util.js"; describe("display_utils", function () { describe("getFilenameFromUrl", function () { @@ -300,4 +302,26 @@ describe("display_utils", function () { }); }); }); + + describe("changeLightness", function () { + it("Check that the lightness is changed correctly", function () { + if (isNodeJS) { + pending("DOM is not supported in Node.js."); + } + const div = document.createElement("div"); + const { style } = div; + style.width = style.height = "0"; + style.backgroundColor = "hsl(123, 45%, 67%)"; + document.body.append(div); + const [r, g, b] = getRGB(getComputedStyle(div).backgroundColor); + div.remove(); + expect([r, g, b]).toEqual([133, 209, 137]); + expect(changeLightness(r, g, b, l => l)).toEqual( + "hsl(123.16, 45.24%, 67.06%)" + ); + expect(changeLightness(r, g, b, l => l / 2)).toEqual( + "hsl(123.16, 45.24%, 33.53%)" + ); + }); + }); }); diff --git a/test/unit/pdf_spec.js b/test/unit/pdf_spec.js index c1409b4b2..45ae6a192 100644 --- a/test/unit/pdf_spec.js +++ b/test/unit/pdf_spec.js @@ -43,6 +43,7 @@ import { version, } from "../../src/display/api.js"; import { + changeLightness, fetchData, getFilenameFromUrl, getPdfFilenameFromUrl, @@ -82,6 +83,7 @@ const expectedAPI = Object.freeze({ AnnotationMode, AnnotationType, build, + changeLightness, ColorPicker, createValidAbsoluteUrl, DOMSVGFactory, diff --git a/web/comment_manager.js b/web/comment_manager.js index f330439db..f2fc485da 100644 --- a/web/comment_manager.js +++ b/web/comment_manager.js @@ -15,6 +15,7 @@ import { AnnotationEditorType, + changeLightness, getRGB, noContextMenu, PDFDateString, @@ -355,9 +356,7 @@ class CommentManager { return null; // No color provided. } const [r, g, b] = getRGB(color); - const gray = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; - const ratio = gray < 0.9 ? Math.round((0.9 - gray) * 100) : 0; - return `color-mix(in srgb, ${ratio}% white, ${color})`; + return changeLightness(r, g, b); } #setText(text) { diff --git a/web/pdfjs.js b/web/pdfjs.js index 4898002fe..13bf17bb7 100644 --- a/web/pdfjs.js +++ b/web/pdfjs.js @@ -23,6 +23,7 @@ const { AnnotationMode, AnnotationType, build, + changeLightness, ColorPicker, createValidAbsoluteUrl, DOMSVGFactory, @@ -78,6 +79,7 @@ export { AnnotationMode, AnnotationType, build, + changeLightness, ColorPicker, createValidAbsoluteUrl, DOMSVGFactory,