From 092d33794859d7432a0362c814847f73b991006b Mon Sep 17 00:00:00 2001 From: hjwforever Date: Fri, 11 Aug 2023 15:43:45 +0800 Subject: [PATCH] release: v2.2.4 --- README.md | 1 + README_EN.md | 1 + package.json | 6 +- src/renderer/components/RGBTextBtn/index.vue | 50 ++++++ src/renderer/lang/en.js | 3 +- src/renderer/lang/ja.js | 3 +- src/renderer/lang/zh.js | 3 +- src/renderer/store/modules/preferenceStore.js | 1 + src/renderer/tools/hotkey.js | 5 + src/renderer/utils/canvas.js | 39 +++++ src/renderer/views/image/Toolbar.vue | 10 +- .../views/image/components/ImageCanvas.vue | 135 +++++++++++++++- src/renderer/views/video/Toolbar.vue | 13 +- .../views/video/components/videoCanvas.vue | 144 ++++++++++++++++-- 14 files changed, 388 insertions(+), 26 deletions(-) create mode 100644 src/renderer/components/RGBTextBtn/index.vue create mode 100644 src/renderer/utils/canvas.js diff --git a/README.md b/README.md index 3ca2078..0c3bc84 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ - [文件选择](https://github.com/MegEngine/MegSpot/wiki/文件选择) - [文件列表](https://github.com/MegEngine/MegSpot/wiki/文件列表) - [文件长廊](https://github.com/MegEngine/MegSpot/wiki/文件长廊) +- [RGB Viewer](https://github.com/MegEngine/MegSpot/wiki/RGB-Viewer) - [命令行操作](https://github.com/MegEngine/MegSpot/wiki/命令行操作) - [多语言支持](https://github.com/MegEngine/MegSpot/wiki/语言支持) - [帮助视频](https://github.com/MegEngine/MegSpot/wiki/帮助视频) diff --git a/README_EN.md b/README_EN.md index b483971..52d8d5d 100644 --- a/README_EN.md +++ b/README_EN.md @@ -17,6 +17,7 @@ English | [中文](README.md) - Mac, Linux, Window cross-platform support & automatic update support. - **HEVC/H.265 video hard decoding support**. - Support the comparison of any screen of multiple videos. +- [RGB Viewer](https://github.com/MegEngine/MegSpot/wiki/RGB-Viewer) - Support terminal command. - Support multiple languages: Chinese, English, Japanese. diff --git a/package.json b/package.json index 8db3645..04fbcdc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "MegSpot", - "version": "v2.2.3", + "version": "v2.2.4", "author": "weiyajun ", "description": "A tool to improve the viewing of pictures and videos by researchers", "homepage": "https://github.com/MegEngine/MegSpot", @@ -44,8 +44,8 @@ "appId": "org.megvii.megspot", "copyright": " Copyright (c) 2022 Megvii Inc. All rights reserved.", "releaseInfo": { - "releaseNotes": "一、 新增功能:\n 1. 图像及视频都支持多序列,并可进行不同序列之间的对比;\n 2. 取色器的色值支持rgb及hex两种显示格式,可在设置中更改; \n二. 优化体验:\n 更新工具栏ui;同步上线相关更新的翻译。 \n三. 修复问题:\n 1. 修复某些图像不能正确同步地缩放的问题;\n 2. 修复快照模式布局未正常加载的问题;", - "releaseDate": "2023.5.14" + "releaseNotes": "新增功能:\n 1. 支持RGB Viewer模式,显示每个像素块的RGB值,图像对比及视频对比(暂停时)均可用;\n 2. 取色器支持显示鼠标x、y坐标信息,可在设置中手动启用/关闭;", + "releaseDate": "2023.08.11" }, "directories": { "output": "build" diff --git a/src/renderer/components/RGBTextBtn/index.vue b/src/renderer/components/RGBTextBtn/index.vue new file mode 100644 index 0000000..3eaf97d --- /dev/null +++ b/src/renderer/components/RGBTextBtn/index.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/renderer/lang/en.js b/src/renderer/lang/en.js index b0b4f91..cfe7bc5 100644 --- a/src/renderer/lang/en.js +++ b/src/renderer/lang/en.js @@ -91,7 +91,8 @@ export default { moveLeft: 'move left', moveRight: 'move right', moveDown: 'move downward', - pickColor: 'Turn on/off the color picker' + pickColor: 'Turn on/off the color picker', + rgbText: 'Enable/disable the display of RGB values in each pixel block' }, dashboard: { entries: { diff --git a/src/renderer/lang/ja.js b/src/renderer/lang/ja.js index 7332277..b34de64 100644 --- a/src/renderer/lang/ja.js +++ b/src/renderer/lang/ja.js @@ -91,7 +91,8 @@ export default { moveLeft: '左に移動', moveRight: '向右移动', moveDown: '下に移動', - pickColor: 'カラーピッカーのオン/オフを切り替えます' + pickColor: 'カラーピッカーのオン/オフを切り替えます', + rgbText: '各ピクセルブロックのRGB値の表示を有効/無効にします。' }, dashboard: { entries: { diff --git a/src/renderer/lang/zh.js b/src/renderer/lang/zh.js index b4a8847..06116d8 100644 --- a/src/renderer/lang/zh.js +++ b/src/renderer/lang/zh.js @@ -90,7 +90,8 @@ export default { moveLeft: '向左移动', moveRight: '向右移动', moveDown: '向下移动', - pickColor: '打开/关闭取色器' + pickColor: '打开/关闭取色器', + rgbText: '启用/关闭RGB数值在每个像素块中的显示' }, dashboard: { entries: { diff --git a/src/renderer/store/modules/preferenceStore.js b/src/renderer/store/modules/preferenceStore.js index e5b819b..c0f0ce7 100644 --- a/src/renderer/store/modules/preferenceStore.js +++ b/src/renderer/store/modules/preferenceStore.js @@ -22,6 +22,7 @@ const preferenceStore = { colorPickerMode: 'rgb', // `rgb` / `hex` // 取色器中是否显示鼠标坐标 colorPickerShowPos: true, + showRGBText: false, // 每次手动按键时图像的移动距离, 默认为100像素 moveDistance: 100, // 视频控制条位置 fixed(固定在toolbar) / float (悬浮球) diff --git a/src/renderer/tools/hotkey.js b/src/renderer/tools/hotkey.js index 5043462..55710d3 100644 --- a/src/renderer/tools/hotkey.js +++ b/src/renderer/tools/hotkey.js @@ -158,6 +158,11 @@ export const DEFAULT_HOTKEYS = [ name: 'right', desc: 'compare to right', keysArr: [['ArrowRight']] + }, + { + name: 'rgbText', + desc: 'display RGB values in pixel blocks', + keysArr: [['c']] } ].map((item, index) => { item.index = index diff --git a/src/renderer/utils/canvas.js b/src/renderer/utils/canvas.js new file mode 100644 index 0000000..42b90f4 --- /dev/null +++ b/src/renderer/utils/canvas.js @@ -0,0 +1,39 @@ +import { clamp } from "./index" + +export const DEFAULT_OVERLAP_RECT_OPTIONS = { padding: 0, round: false } +function getOverlapRect(rect1, rect2, options = {}) { + const { padding, round } = Object.assign({}, DEFAULT_OVERLAP_RECT_OPTIONS, options) + let x1 = Math.max(rect1.x, rect2.x) + let y1 = Math.max(rect1.y, rect2.y) + let x2 = Math.min(rect1.x + rect1.width, rect2.x + rect2.width) + let y2 = Math.min(rect1.y + rect1.height, rect2.y + rect2.height) + + if (round) { + x1 = Math.ceil(x1) + y1 = Math.ceil(y1) + x2 = Math.floor(x2) + y2 = Math.floor(y2) + } + + if (padding !== 0) { + x1 -= padding + y1 -= padding + x2 += padding + y2 += padding + } + + // if (round || padding !== 0) { + // x1 = clamp(x1, Math.min()) + // y1 = padding + // x2 = padding + // y2 = padding + // } + + if (x2 > x1 && y2 > y1) { + return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 } + } else { + return null + } +} + +export { getOverlapRect } diff --git a/src/renderer/views/image/Toolbar.vue b/src/renderer/views/image/Toolbar.vue index 4765b7d..dac0f76 100644 --- a/src/renderer/views/image/Toolbar.vue +++ b/src/renderer/views/image/Toolbar.vue @@ -32,6 +32,7 @@ {{ $t('imageCenter.nearestInterpolation') }} {{ $t('imageCenter.bilinearInterpolation') }} + { + this.setPreference({ showRGBText: !this.preference.showRGBText }) + }) hotkeyUpEvents.set('top', () => { this.cancelOverlay(GLOBAL_CONSTANTS.DIRECTION_TOP) diff --git a/src/renderer/views/image/components/ImageCanvas.vue b/src/renderer/views/image/components/ImageCanvas.vue index 6e3fd9a..866f39a 100644 --- a/src/renderer/views/image/components/ImageCanvas.vue +++ b/src/renderer/views/image/components/ImageCanvas.vue @@ -64,6 +64,7 @@ import { createNamespacedHelpers } from 'vuex' const { mapGetters, mapActions } = createNamespacedHelpers('imageStore') const { mapGetters: preferenceMapGetters } = createNamespacedHelpers('preferenceStore') import { getImageUrlSyncNoCache } from '@/utils/image' +import { getOverlapRect } from '@/utils/canvas' import { throttle, debounce } from '@/utils' import { SCALE_CONSTANTS, DRAG_CONSTANTS } from '@/constants' import chokidar from 'chokidar' @@ -128,6 +129,8 @@ export default { height: 0 }, bitMap: null, + imgMat: null, + imgMatRequestId: null, imagePosition: null, cachedPositionData: null, imgScale: 'N/A', @@ -277,6 +280,9 @@ export default { outputs: newVal }) } + }, + imgScaleNum() { + return !isNaN(this.imgScale) ? Number(this.imgScale) : 0 } }, async mounted() { @@ -288,6 +294,10 @@ export default { }, beforeDestroy() { this.removeEvents() + if (this.imgMat) { + this.imgMat?.delete() + this.imgMat = null + } this.bitMap && this.bitMap?.close() this.initFilters() }, @@ -327,6 +337,11 @@ export default { this.setSmooth() }, immediate: true + }, + 'preference.showRGBText': { + handler(newVal, oldVal) { + this.drawImage() + } } }, methods: { @@ -430,20 +445,42 @@ export default { }, initHist(reGenerate = true, config = null) { return new Promise((resolve, reject) => { - const cv = window?.cv - if (cv && this.image?.width) { + let mat + const cv = this.$cv + if (this.imgMat) { + mat = this.imgMat.clone() + } else if (cv && this.image?.width) { + mat = cv.imread(this.image) + } + if (mat) { this.currentHist = reGenerate - ? this.$refs['hist-container'].reGenerateHist(cv.imread(this.image), config) - : this.$refs['hist-container'].generateHist(cv.imread(this.image), config) + ? this.$refs['hist-container'].reGenerateHist(mat, config) + : this.$refs['hist-container'].generateHist(mat, config) resolve() } return reject() }) }, handleChangeHistTypes(config) { - window?.cv?.Mat && this.initHist(false, config) + this.$cv?.Mat && this.initHist(false, config) this.$refs['hist-container'].setVisible(true) }, + async initImageMat() { + const cv = this.$cv + if (this.imgMatRequestId) { + cancelAnimationFrame(this.imgMatRequestId) + this.imgMatRequestId = 0 + } + if (cv && this.image?.width) { + if (this.imgMat) { + this.imgMat?.delete() + this.imgMat = null + } + this.imgMat = cv.imread(this.image) + } else { + this.imgMatRequestId = requestAnimationFrame(this.initImageMat) + } + }, async initBitMap(_imageData) { return new Promise(async (resolve) => { if (this.bitMap) { @@ -556,6 +593,7 @@ export default { const imageData = toRGBA8(ifd) this.image = new Image(ifd.width, ifd.height) await this.initBitMap(imageData) + this.initImageMat() this.loading = false this.ready = true this.reDraw(initPosition) @@ -564,6 +602,7 @@ export default { this.image = new Image() this.image.onload = async () => { await this.initBitMap() + this.initImageMat() this.loading = false this.ready = true this.reDraw(initPosition) @@ -584,10 +623,91 @@ export default { this.initCanvas() this.initImage() }, + pickCanvasColor(x, y) { + if (!this.cs) { + console.log('this.cs') + return null + } + + const pixel = this.cs.getImageData(x, y, 1, 1) + let [R, G, B, A] = pixel.data + A = parseInt(A / 255) + return { + R, + G, + B, + A, + isBright: (0.299 * R + 0.587 * G + 0.114 * B) / 255 >= 0.8 + } + }, + async drawRGBText() { + const cv = this.$cv + if (this.preference.showRGBText && cv && this.imgMat && this.imagePosition && this.image && this.cs && this.imgScaleNum >= 42) { + if (this.drawRGBTextReqId) { + cancelAnimationFrame(this.drawRGBTextReqId) + this.drawRGBTextReqId = null + } + + const that = this + this.drawRGBTextReqId = requestAnimationFrame(() => { + const imgScaleNum = that.imgScaleNum + const { x, y } = that.imagePosition + + const viewerRect = { x: 0, y: 0, width: that._width, height: that._height } + const overlapRect = getOverlapRect(that.imagePosition, viewerRect) + if (!overlapRect) { + return + } + Object.assign(overlapRect, { + x: Math.max(0, Math.floor((overlapRect.x - x) / imgScaleNum) - 1), + y: Math.max(0, Math.floor((overlapRect.y - y) / imgScaleNum) - 1), + width: Math.min(that.imgMat.cols, Math.ceil(overlapRect.width / imgScaleNum) + 1), + height: Math.min(that.imgMat.rows, Math.ceil(overlapRect.height / imgScaleNum) + 1) + }) + + const fontSize = Math.floor((imgScaleNum * 0.8) / 3) + const gap = 2 + const padding = (imgScaleNum - fontSize * 3 - gap * 2) / 2 + + that.cs.restore() + that.cs.save() + that.cs.font = `bold ${fontSize}px Arial` + that.cs.fillStyle = 'black' + that.cs.textAlign = 'center' + + const rect = new cv.Rect(overlapRect.x, overlapRect.y, overlapRect.width, overlapRect.height) + const roi = that.imgMat.roi(rect) + const channelCount = that.imgMat.channels() + + const drawOffset1 = padding + fontSize + const drawOffset2 = drawOffset1 + fontSize + gap + const drawOffset3 = drawOffset2 + fontSize + gap + + for (let row = 0; row < roi.rows; row++) { + for (let col = 0; col < roi.cols; col++) { + let pixelValue = [] + for (let ch = 0; ch < channelCount; ch++) { + pixelValue.push(roi.ucharPtr(row, col)[ch]) + } + const [R, G, B] = pixelValue + const isBright = (0.299 * R + 0.587 * G + 0.114 * B) / 255 >= 0.8 + that.cs.fillStyle = isBright ? 'black' : 'white' + const _x = x + imgScaleNum * (overlapRect.x + col + 0.5) + const _y = y + imgScaleNum * (overlapRect.y + row) + !isNaN(R) && that.cs.fillText(`R: ${R}`, _x, _y + drawOffset1) + !isNaN(G) && that.cs.fillText(`G: ${G}`, _x, _y + drawOffset2) + !isNaN(B) && that.cs.fillText(`B: ${B}`, _x, _y + drawOffset3) + } + } + that.cs.restore() + }) + } + }, async drawImage(img = null) { let { x, y, width, height } = this.imagePosition || this.getImageInitPos(this.canvas, this.image) this.cs.clearRect(0, 0, this.canvas.width, this.canvas.height) this.cs.drawImage(img ?? this.bitMap, x, y, width, height) + this.drawRGBText() }, handleClick() { this.triggerRGB && this.$refs['zoom-viewer']?.copyColor() @@ -614,7 +734,7 @@ export default { return } - window?.cv?.Mat && + this.$cv?.Mat && this.initHist().then(() => { this.$refs['hist-container'].setVisible(visible) }) @@ -732,7 +852,7 @@ export default { } this.drawImage() this.doZoomEnd() - this.$refs['hist-container'].visible && window?.cv?.Mat && this.initHist() + this.$refs['hist-container'].visible && this.$cv?.Mat && this.initHist() }) }, snapshotModeInitPos() { @@ -867,6 +987,7 @@ export default { width } this.imgScale = 'N/A' + this.doZoomEnd() this.drawImage() } }, diff --git a/src/renderer/views/video/Toolbar.vue b/src/renderer/views/video/Toolbar.vue index 4ae7319..61d89e8 100644 --- a/src/renderer/views/video/Toolbar.vue +++ b/src/renderer/views/video/Toolbar.vue @@ -30,6 +30,7 @@ {{ $t('imageCenter.nearestInterpolation') }} {{ $t('imageCenter.bilinearInterpolation') }} +
@@ -270,8 +271,9 @@ import SelectedBtn from '@/components/selected-btn' import { createNamespacedHelpers } from 'vuex' import GifDialog from '@/components/gif-dialog' import ImageSetting from '@/components/image-setting' +import RGBTextBtn from '@//components/RGBTextBtn' const { mapGetters, mapActions } = createNamespacedHelpers('videoStore') -const { mapGetters: preferenceMapGetters } = createNamespacedHelpers('preferenceStore') +const { mapGetters: preferenceMapGetters, mapActions: preferenceMapActions } = createNamespacedHelpers('preferenceStore') import { throttle } from '@/utils' import { handleEvent } from '@/tools/hotkey' import { TimeManager } from '@/utils/video' @@ -321,7 +323,7 @@ export default { hotkeyUpEvents: undefined } }, - components: { SelectedBtn, GifDialog, ImageSetting }, + components: { SelectedBtn, GifDialog, ImageSetting, RGBTextBtn }, async mounted() { this.initHotkeyEvents() window.addEventListener('keydown', this.handleHotKey, true) @@ -337,6 +339,7 @@ export default { }, methods: { ...mapActions(['emptyVideos', 'removeVideos', 'setVideoConfig', 'setVideos']), + ...preferenceMapActions(['setPreference']), initHotkeyEvents() { this.hotkeyDownEvents = new Map([ [ @@ -456,6 +459,12 @@ export default { data: { offset: { x: -this.preference.moveDistance, y: 0 } } }) } + ], + [ + 'rgbText', + () => { + this.setPreference({ showRGBText: !this.preference.showRGBText }) + } ] ]) diff --git a/src/renderer/views/video/components/videoCanvas.vue b/src/renderer/views/video/components/videoCanvas.vue index 09c19f1..8ec0683 100644 --- a/src/renderer/views/video/components/videoCanvas.vue +++ b/src/renderer/views/video/components/videoCanvas.vue @@ -155,6 +155,7 @@ import chokidar from 'chokidar' import * as CONSTANTS from '../video-constants' import { getFileName } from '@/filter/get-file-name' import { TimeManager } from '@/utils/video' +import { getOverlapRect } from '@/utils/canvas' import { throttle, getUuidv4 } from '@/utils' import { useWorker } from '@/utils/worker' @@ -223,6 +224,7 @@ export default { height: 0 }, bitMap: null, + imgMat: null, imagePosition: null, cachedPositionData: null, imgScale: 'N/A', @@ -404,6 +406,9 @@ export default { backgroundColor: 'red', opacity: 0.5 } + }, + imgScaleNum() { + return !isNaN(this.imgScale) ? Number(this.imgScale) : 0 } }, async mounted() { @@ -429,6 +434,10 @@ export default { this.video.remove() this.video = null } + if (this.imgMat) { + this.imgMat?.delete() + this.imgMat = null + } this.bitMap && this.bitMap.close() // this.initFilters() }, @@ -457,6 +466,7 @@ export default { .on('change', (path, details) => { console.log('video--change', path, details) // this.initbitMap(); + // this.initImageMat() this.initVideo() }) .on('unlink', (path, details) => { @@ -502,6 +512,11 @@ export default { this.updateZoomViewer = throttle(1000 / newRate, function () { this.$refs['zoom-viewer']?.draw(...arguments) }) + }, + 'preference.showRGBText': { + handler(newVal, oldVal) { + this.drawImage() + } } }, methods: { @@ -539,6 +554,7 @@ export default { } if (this.paused && this.video?.videoWidth) { this.initbitMap() + this.initImageMat() } else { this.drawImage() } @@ -699,6 +715,7 @@ export default { this.paused = true if (this.paused) { this.initbitMap() + this.initImageMat() } }, handleVideoResetTime() { @@ -743,6 +760,7 @@ export default { // console.log('offset', this.offset) this.changeFrameUpdateFN(currentTime) // this.initbitMap() + // this.initImageMat() }, handleBroadcast({ name, data = { id: null } }) { if (this.selectedId) { @@ -834,18 +852,32 @@ export default { // } return }, + getVideoImageData() { + const histCanvas = document.createElement('canvas') + histCanvas.width = this.video.videoWidth + histCanvas.height = this.video.videoHeight + const histCanvasCtx = histCanvas.getContext('2d') + histCanvasCtx.drawImage(this.video, 0, 0) + const imgData = histCanvasCtx.getImageData(0, 0, histCanvas.width, histCanvas.height) + return imgData + }, generateHist(reGenerate = true, config = null) { return new Promise((resolve, reject) => { try { - const histCanvas = document.createElement('canvas') - histCanvas.width = this.video.videoWidth - histCanvas.height = this.video.videoHeight - let histCanvasCtx = histCanvas.getContext('2d') - histCanvasCtx.drawImage(this.video, 0, 0) - this.currentHist = reGenerate - ? this.$refs['hist-container'].reGenerateHist(window.cv.imread(histCanvas), config) - : this.$refs['hist-container'].generateHist(window.cv.imread(histCanvas), config) - resolve() + let mat + const cv = this.$cv + if (this.imgMat) { + mat = this.imgMat.clone() + } else if (cv) { + const imgData = this.getVideoImageData() + mat = cv.matFromImageData(imgData) + } + if (mat) { + this.currentHist = reGenerate + ? this.$refs['hist-container'].reGenerateHist(mat, config) + : this.$refs['hist-container'].generateHist(mat, config) + resolve() + } } catch (err) { console.log('err', err) } @@ -879,6 +911,7 @@ export default { this.video.addEventListener('ended', () => { this.currentTime = this.video.currentTime this.initbitMap() + this.initImageMat() this.$emit('ended') }) @@ -917,6 +950,7 @@ export default { this.doZoomEnd() this.loading = false await this.initbitMap() + this.initImageMat() this.reset() resolve() }) @@ -984,12 +1018,101 @@ export default { console.log('reMount') this.initCanvas() await this.initbitMap() + this.initImageMat() this.reset() }, clearCanvas() { const maxLen = this.canvas.width * this.canvas.height * 4 this.cs.clearRect(-maxLen, -maxLen, 2 * maxLen, 2 * maxLen) }, + async initImageMat() { + const cv = this.$cv + if (this.imgMatRequestId) { + cancelAnimationFrame(this.imgMatRequestId) + this.imgMatRequestId = 0 + } + if (cv && this.video?.videoWidth) { + if (this.imgMat) { + this.imgMat?.delete() + this.imgMat = null + } + const imgData = this.getVideoImageData() + this.imgMat = cv.matFromImageData(imgData) + } else { + this.imgMatRequestId = requestAnimationFrame(this.initImageMat) + } + }, + async drawRGBText() { + const cv = this.$cv + if ( + this.paused && + this.preference.showRGBText && + cv && + this.imgMat && + this.imagePosition && + this.cs && + this.imgScaleNum >= 42 + ) { + if (this.drawRGBTextReqId) { + cancelAnimationFrame(this.drawRGBTextReqId) + this.drawRGBTextReqId = null + } + + const that = this + this.drawRGBTextReqId = requestAnimationFrame(() => { + const imgScaleNum = that.imgScaleNum + const { x, y } = that.imagePosition + + const viewerRect = { x: 0, y: 0, width: that._width, height: that._height } + const overlapRect = getOverlapRect(that.imagePosition, viewerRect) + if (!overlapRect) { + return + } + Object.assign(overlapRect, { + x: Math.max(0, Math.floor((overlapRect.x - x) / imgScaleNum) - 1), + y: Math.max(0, Math.floor((overlapRect.y - y) / imgScaleNum) - 1), + width: Math.min(that.imgMat.cols, Math.ceil(overlapRect.width / imgScaleNum) + 1), + height: Math.min(that.imgMat.rows, Math.ceil(overlapRect.height / imgScaleNum) + 1) + }) + + const fontSize = Math.floor((imgScaleNum * 0.8) / 3) + const gap = 2 + const padding = (imgScaleNum - fontSize * 3 - gap * 2) / 2 + + that.cs.restore() + that.cs.save() + that.cs.font = `bold ${fontSize}px Arial` + that.cs.fillStyle = 'black' + that.cs.textAlign = 'center' + + const rect = new cv.Rect(overlapRect.x, overlapRect.y, overlapRect.width, overlapRect.height) + const roi = that.imgMat.roi(rect) + const channelCount = that.imgMat.channels() + + const drawOffset1 = padding + fontSize + const drawOffset2 = drawOffset1 + fontSize + gap + const drawOffset3 = drawOffset2 + fontSize + gap + + for (let row = 0; row < roi.rows; row++) { + for (let col = 0; col < roi.cols; col++) { + let pixelValue = [] + for (let ch = 0; ch < channelCount; ch++) { + pixelValue.push(roi.ucharPtr(row, col)[ch]) + } + const [R, G, B] = pixelValue + const isBright = (0.299 * R + 0.587 * G + 0.114 * B) / 255 >= 0.8 + that.cs.fillStyle = isBright ? 'black' : 'white' + const _x = x + imgScaleNum * (overlapRect.x + col + 0.5) + const _y = y + imgScaleNum * (overlapRect.y + row) + !isNaN(R) && that.cs.fillText(`R: ${R}`, _x, _y + drawOffset1) + !isNaN(G) && that.cs.fillText(`G: ${G}`, _x, _y + drawOffset2) + !isNaN(B) && that.cs.fillText(`B: ${B}`, _x, _y + drawOffset3) + } + } + that.cs.restore() + }) + } + }, drawImage(img) { if (!this.imagePosition) return let { x, y, width, height } = this.imagePosition @@ -1008,6 +1131,8 @@ export default { if (this.dynamicPickColor && this.triggerRGB) { this.doHandleMove() } + + this.drawRGBText() }, handleClick() { this.triggerRGB && this.$refs['zoom-viewer']?.copyColor() @@ -1274,6 +1399,7 @@ export default { } this.imgScale = 'N/A' } + this.doZoomEnd() this.drawImage() }, doZoomEnd() {