|
|
@@ -0,0 +1,388 @@
|
|
|
+// nativeplugins/opencv/android/uts/BitmapUtils.uts
|
|
|
+
|
|
|
+import Bitmap from 'android.graphics.Bitmap'
|
|
|
+import Base64 from 'android.util.Base64'
|
|
|
+import BitmapFactory from 'android.graphics.BitmapFactory'
|
|
|
+
|
|
|
+// 定义类型接口
|
|
|
+type ImageClarityScore = {
|
|
|
+ score: number
|
|
|
+ level: string
|
|
|
+}
|
|
|
+
|
|
|
+type ImageClarityResult = {
|
|
|
+ score: number
|
|
|
+ level: string
|
|
|
+ isClear: boolean
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 图像清晰度检测工具类
|
|
|
+ */
|
|
|
+export class BitmapUtils {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算图像的亮度方差作为清晰度指标
|
|
|
+ */
|
|
|
+ static calculateBrightnessVariance(bitmap: Bitmap): number {
|
|
|
+ try {
|
|
|
+ const width = bitmap.getWidth()
|
|
|
+ const height = bitmap.getHeight()
|
|
|
+
|
|
|
+ // 根据原始尺寸动态计算采样尺寸
|
|
|
+ // 目标采样区域的总像素数,控制在合理范围内
|
|
|
+ const targetPixelCount = 2500 // 50x50 = 2500像素
|
|
|
+
|
|
|
+ // 计算合适的采样尺寸,保持宽高比
|
|
|
+ let sampleWidth: number
|
|
|
+ let sampleHeight: number
|
|
|
+
|
|
|
+ if (width > height) {
|
|
|
+ // 宽图
|
|
|
+ sampleWidth = Math.min(width, Math.sqrt(targetPixelCount * width / height).toInt())
|
|
|
+ sampleHeight = Math.min(height, (sampleWidth * height / width).toInt())
|
|
|
+ } else {
|
|
|
+ // 高图或方图
|
|
|
+ sampleHeight = Math.min(height, Math.sqrt(targetPixelCount * height / width).toInt())
|
|
|
+ sampleWidth = Math.min(width, (sampleHeight * width / height).toInt())
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保最小尺寸
|
|
|
+ sampleWidth = Math.max(sampleWidth, 32)
|
|
|
+ sampleHeight = Math.max(sampleHeight, 32)
|
|
|
+
|
|
|
+ const scaledBitmap = Bitmap.createScaledBitmap(
|
|
|
+ bitmap,
|
|
|
+ sampleWidth.toInt(),
|
|
|
+ sampleHeight.toInt(),
|
|
|
+ false
|
|
|
+ )
|
|
|
+
|
|
|
+ let totalBrightness = 0.0
|
|
|
+ let brightnessSquares = 0.0
|
|
|
+ const pixelCount = sampleWidth * sampleHeight
|
|
|
+
|
|
|
+ // 计算平均亮度
|
|
|
+ for (let i: number = 0; i < sampleWidth; i++) {
|
|
|
+ for (let j: number = 0; j < sampleHeight; j++) {
|
|
|
+ const pixel = scaledBitmap.getPixel(i.toInt(), j.toInt())
|
|
|
+ const brightness = this.getPixelBrightness(pixel)
|
|
|
+ totalBrightness += brightness
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const meanBrightness = totalBrightness / pixelCount
|
|
|
+
|
|
|
+ // 计算亮度方差
|
|
|
+ for (let i: number = 0; i < sampleWidth; i++) {
|
|
|
+ for (let j: number = 0; j < sampleHeight; j++) {
|
|
|
+ const pixel = scaledBitmap.getPixel(i.toInt(), j.toInt())
|
|
|
+ const brightness = this.getPixelBrightness(pixel)
|
|
|
+ const diff = brightness - meanBrightness
|
|
|
+ brightnessSquares += diff * diff
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const variance = brightnessSquares / pixelCount
|
|
|
+
|
|
|
+ // 回收临时Bitmap
|
|
|
+ if (!scaledBitmap.isRecycled()) {
|
|
|
+ scaledBitmap.recycle()
|
|
|
+ }
|
|
|
+
|
|
|
+ return variance
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('清晰度计算失败:', error)
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在calculateBrightnessVariance方法中使用多尺度采样
|
|
|
+ static calculateBrightnessVariance2(bitmap: Bitmap): number {
|
|
|
+ try {
|
|
|
+ const width = bitmap.getWidth()
|
|
|
+ const height = bitmap.getHeight()
|
|
|
+
|
|
|
+ // 对2048×1536的图片,使用多个采样尺度
|
|
|
+ const scales = [
|
|
|
+ { width: 200, height: 150 }, // 约10%尺度
|
|
|
+ { width: 100, height: 75 }, // 约5%尺度
|
|
|
+ { width: 50, height: 38 } // 约2.5%尺度
|
|
|
+ ]
|
|
|
+
|
|
|
+ let totalVariance = 0.0
|
|
|
+ let scaleCount = 0
|
|
|
+
|
|
|
+ for (const scale of scales) {
|
|
|
+ const sampleWidth = Math.min(width, scale.width as number)
|
|
|
+ const sampleHeight = Math.min(height, scale.height as number)
|
|
|
+
|
|
|
+ if (sampleWidth < 32 || sampleHeight < 32) continue
|
|
|
+
|
|
|
+ const scaledBitmap = Bitmap.createScaledBitmap(
|
|
|
+ bitmap,
|
|
|
+ sampleWidth.toInt(),
|
|
|
+ sampleHeight.toInt(),
|
|
|
+ false
|
|
|
+ )
|
|
|
+
|
|
|
+ const variance = this.calculateVarianceForBitmap(scaledBitmap)
|
|
|
+ totalVariance += variance
|
|
|
+ scaleCount++
|
|
|
+
|
|
|
+ if (!scaledBitmap.isRecycled()) {
|
|
|
+ scaledBitmap.recycle()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return scaleCount > 0 ? totalVariance / scaleCount : -1
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('多尺度清晰度计算失败:', error)
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取方差计算为独立方法
|
|
|
+ private static calculateVarianceForBitmap(bitmap: Bitmap): number {
|
|
|
+ const width = bitmap.getWidth()
|
|
|
+ const height = bitmap.getHeight()
|
|
|
+
|
|
|
+ let totalBrightness = 0.0
|
|
|
+ let brightnessSquares = 0.0
|
|
|
+ const pixelCount = width * height
|
|
|
+
|
|
|
+ // 计算平均亮度
|
|
|
+ for (let i: number = 0; i < width; i++) {
|
|
|
+ for (let j: number = 0; j < height; j++) {
|
|
|
+ const pixel = bitmap.getPixel(i.toInt(), j.toInt())
|
|
|
+ const brightness = this.getPixelBrightness(pixel)
|
|
|
+ totalBrightness += brightness
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const meanBrightness = totalBrightness / pixelCount
|
|
|
+
|
|
|
+ // 计算亮度方差
|
|
|
+ for (let i: number = 0; i < width; i++) {
|
|
|
+ for (let j: number = 0; j < height; j++) {
|
|
|
+ const pixel = bitmap.getPixel(i.toInt(), j.toInt())
|
|
|
+ const brightness = this.getPixelBrightness(pixel)
|
|
|
+ const diff = brightness - meanBrightness
|
|
|
+ brightnessSquares += diff * diff
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return brightnessSquares / pixelCount
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用边缘密度作为清晰度指标(更适合大图)
|
|
|
+ */
|
|
|
+ static calculateEdgeDensity(bitmap: Bitmap): number {
|
|
|
+ try {
|
|
|
+ const width = bitmap.getWidth()
|
|
|
+ const height = bitmap.getHeight()
|
|
|
+
|
|
|
+ // 对于大图,使用适当的采样尺寸
|
|
|
+ const sampleWidth = Math.min(width, 300)
|
|
|
+ const sampleHeight = Math.min(height, (300 * height / width).toInt())
|
|
|
+
|
|
|
+ const scaledBitmap = Bitmap.createScaledBitmap(
|
|
|
+ bitmap,
|
|
|
+ sampleWidth.toInt(),
|
|
|
+ sampleHeight.toInt(),
|
|
|
+ false
|
|
|
+ )
|
|
|
+
|
|
|
+ let edgeCount = 0
|
|
|
+ const totalPixels = sampleWidth * sampleHeight
|
|
|
+
|
|
|
+ // 简单的边缘检测
|
|
|
+ for (let i: number = 1; i < sampleWidth - 1; i++) {
|
|
|
+ for (let j: number = 1; j < sampleHeight - 1; j++) {
|
|
|
+ const center = this.getPixelBrightness(scaledBitmap.getPixel(i.toInt(), j.toInt()))
|
|
|
+ const right = this.getPixelBrightness(scaledBitmap.getPixel((i + 1).toInt(), j.toInt()))
|
|
|
+ const bottom = this.getPixelBrightness(scaledBitmap.getPixel(i.toInt(), (j + 1).toInt()))
|
|
|
+
|
|
|
+ // 如果相邻像素亮度差异大,认为是边缘
|
|
|
+ if (Math.abs(center - right) > 20 || Math.abs(center - bottom) > 20) {
|
|
|
+ edgeCount++
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const edgeDensity = (edgeCount / totalPixels) * 1000
|
|
|
+
|
|
|
+ if (!scaledBitmap.isRecycled()) {
|
|
|
+ scaledBitmap.recycle()
|
|
|
+ }
|
|
|
+
|
|
|
+ return edgeDensity
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('边缘密度计算失败:', error)
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取像素亮度
|
|
|
+ */
|
|
|
+ private static getPixelBrightness(pixel: number): number {
|
|
|
+ const r = (pixel >> 16) & 0xff
|
|
|
+ const g = (pixel >> 8) & 0xff
|
|
|
+ const b = pixel & 0xff
|
|
|
+ return (r + g + b) / 3.0
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断图像是否清晰
|
|
|
+ */
|
|
|
+ static isImageClear(bitmap: Bitmap, threshold: number = 500): boolean {
|
|
|
+ const variance = this.calculateBrightnessVariance2(bitmap)
|
|
|
+ return variance > threshold
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取图像清晰度评分
|
|
|
+ */
|
|
|
+ static getImageClarityScore(bitmap: Bitmap): ImageClarityScore {
|
|
|
+ const variance = this.calculateBrightnessVariance2(bitmap)
|
|
|
+
|
|
|
+ let level: string
|
|
|
+ if (variance < 0) {
|
|
|
+ level = "处理失败"
|
|
|
+ } else if (variance < 500) {
|
|
|
+ level = "非常模糊"
|
|
|
+ } else if (variance < 2000) {
|
|
|
+ level = "模糊"
|
|
|
+ } else if (variance < 3200) {
|
|
|
+ level = "一般"
|
|
|
+ } else if (variance < 3500) {
|
|
|
+ level = "清晰"
|
|
|
+ } else {
|
|
|
+ level = "非常清晰"
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ score: Math.round(variance * 100) / 100,
|
|
|
+ level: level
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从Base64数据检测清晰度
|
|
|
+ */
|
|
|
+ static checkImageClarityFromBase64(base64Data: string): ImageClarityResult {
|
|
|
+ try {
|
|
|
+ const bitmap = this.base64ToBitmap(base64Data)
|
|
|
+ if (bitmap == null) {
|
|
|
+ return { score: -1, level: "加载失败", isClear: false }
|
|
|
+ }
|
|
|
+
|
|
|
+ const clarityInfo = this.getImageClarityScore(bitmap)
|
|
|
+ const isClear = this.isImageClear(bitmap)
|
|
|
+
|
|
|
+ if (!bitmap.isRecycled()) {
|
|
|
+ bitmap.recycle()
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ score: clarityInfo.score,
|
|
|
+ level: clarityInfo.level,
|
|
|
+ isClear: isClear
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('图像清晰度检测失败:', error)
|
|
|
+ return { score: -1, level: "检测失败", isClear: false }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从文件路径检测清晰度
|
|
|
+ */
|
|
|
+ static checkImageClarityFromPath(imagePath: string): ImageClarityResult {
|
|
|
+ try {
|
|
|
+ const bitmap = BitmapFactory.decodeFile(imagePath)
|
|
|
+ if (bitmap == null) {
|
|
|
+ return { score: -1, level: "加载失败", isClear: false }
|
|
|
+ }
|
|
|
+
|
|
|
+ const clarityInfo = this.getImageClarityScore(bitmap)
|
|
|
+ const isClear = this.isImageClear(bitmap)
|
|
|
+
|
|
|
+ if (!bitmap.isRecycled()) {
|
|
|
+ bitmap.recycle()
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ score: clarityInfo.score,
|
|
|
+ level: clarityInfo.level,
|
|
|
+ isClear: isClear
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('图像清晰度检测失败:', error)
|
|
|
+ return { score: -1, level: "检测失败", isClear: false }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 简化的Base64转Bitmap方法
|
|
|
+ */
|
|
|
+ private static base64ToBitmap(base64Data: string): Bitmap | null {
|
|
|
+ try {
|
|
|
+ // 移除Base64前缀
|
|
|
+ let pureBase64 = base64Data
|
|
|
+ if (base64Data.includes(',')) {
|
|
|
+ pureBase64 = base64Data.split(',')[1]
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解码Base64
|
|
|
+ const decodedBytes = Base64.decode(pureBase64, Base64.DEFAULT)
|
|
|
+
|
|
|
+ // 获取字节数组长度
|
|
|
+ const bitmap = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size)
|
|
|
+
|
|
|
+ return bitmap
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Base64转Bitmap失败:', error)
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 备用方法:直接返回清晰度分数(避免复杂对象类型)
|
|
|
+ */
|
|
|
+ static getSimpleClarityScore(bitmap: Bitmap): number {
|
|
|
+ return this.calculateBrightnessVariance(bitmap)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从Base64获取简单清晰度分数
|
|
|
+ */
|
|
|
+ static getSimpleClarityScoreFromBase64(base64Data: string): number {
|
|
|
+ try {
|
|
|
+ const bitmap = this.base64ToBitmap(base64Data)
|
|
|
+ if (bitmap == null) {
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+
|
|
|
+ const score = this.calculateBrightnessVariance(bitmap)
|
|
|
+
|
|
|
+ if (!bitmap.isRecycled()) {
|
|
|
+ bitmap.recycle()
|
|
|
+ }
|
|
|
+
|
|
|
+ return score
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('清晰度检测失败:', error)
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|