BitmapUtils.uts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. // nativeplugins/opencv/android/uts/BitmapUtils.uts
  2. import Bitmap from 'android.graphics.Bitmap'
  3. import Base64 from 'android.util.Base64'
  4. import BitmapFactory from 'android.graphics.BitmapFactory'
  5. // 定义类型接口
  6. type ImageClarityScore = {
  7. score: number
  8. level: string
  9. }
  10. type ImageClarityResult = {
  11. score: number
  12. level: string
  13. isClear: boolean
  14. }
  15. /**
  16. * 图像清晰度检测工具类
  17. */
  18. export class BitmapUtils {
  19. /**
  20. * 计算图像的亮度方差作为清晰度指标
  21. */
  22. static calculateBrightnessVariance(bitmap: Bitmap): number {
  23. try {
  24. const width = bitmap.getWidth()
  25. const height = bitmap.getHeight()
  26. // 根据原始尺寸动态计算采样尺寸
  27. // 目标采样区域的总像素数,控制在合理范围内
  28. const targetPixelCount = 2500 // 50x50 = 2500像素
  29. // 计算合适的采样尺寸,保持宽高比
  30. let sampleWidth: number
  31. let sampleHeight: number
  32. if (width > height) {
  33. // 宽图
  34. sampleWidth = Math.min(width, Math.sqrt(targetPixelCount * width / height).toInt())
  35. sampleHeight = Math.min(height, (sampleWidth * height / width).toInt())
  36. } else {
  37. // 高图或方图
  38. sampleHeight = Math.min(height, Math.sqrt(targetPixelCount * height / width).toInt())
  39. sampleWidth = Math.min(width, (sampleHeight * width / height).toInt())
  40. }
  41. // 确保最小尺寸
  42. sampleWidth = Math.max(sampleWidth, 32)
  43. sampleHeight = Math.max(sampleHeight, 32)
  44. const scaledBitmap = Bitmap.createScaledBitmap(
  45. bitmap,
  46. sampleWidth.toInt(),
  47. sampleHeight.toInt(),
  48. false
  49. )
  50. let totalBrightness = 0.0
  51. let brightnessSquares = 0.0
  52. const pixelCount = sampleWidth * sampleHeight
  53. // 计算平均亮度
  54. for (let i: number = 0; i < sampleWidth; i++) {
  55. for (let j: number = 0; j < sampleHeight; j++) {
  56. const pixel = scaledBitmap.getPixel(i.toInt(), j.toInt())
  57. const brightness = this.getPixelBrightness(pixel)
  58. totalBrightness += brightness
  59. }
  60. }
  61. const meanBrightness = totalBrightness / pixelCount
  62. // 计算亮度方差
  63. for (let i: number = 0; i < sampleWidth; i++) {
  64. for (let j: number = 0; j < sampleHeight; j++) {
  65. const pixel = scaledBitmap.getPixel(i.toInt(), j.toInt())
  66. const brightness = this.getPixelBrightness(pixel)
  67. const diff = brightness - meanBrightness
  68. brightnessSquares += diff * diff
  69. }
  70. }
  71. const variance = brightnessSquares / pixelCount
  72. // 回收临时Bitmap
  73. if (!scaledBitmap.isRecycled()) {
  74. scaledBitmap.recycle()
  75. }
  76. return variance
  77. } catch (error) {
  78. console.error('清晰度计算失败:', error)
  79. return -1
  80. }
  81. }
  82. // 在calculateBrightnessVariance方法中使用多尺度采样
  83. static calculateBrightnessVariance2(bitmap: Bitmap): number {
  84. try {
  85. const width = bitmap.getWidth()
  86. const height = bitmap.getHeight()
  87. // 对2048×1536的图片,使用多个采样尺度
  88. const scales = [
  89. { width: 200, height: 150 }, // 约10%尺度
  90. { width: 100, height: 75 }, // 约5%尺度
  91. { width: 50, height: 38 } // 约2.5%尺度
  92. ]
  93. let totalVariance = 0.0
  94. let scaleCount = 0
  95. for (const scale of scales) {
  96. const sampleWidth = Math.min(width, scale.width as number)
  97. const sampleHeight = Math.min(height, scale.height as number)
  98. if (sampleWidth < 32 || sampleHeight < 32) continue
  99. const scaledBitmap = Bitmap.createScaledBitmap(
  100. bitmap,
  101. sampleWidth.toInt(),
  102. sampleHeight.toInt(),
  103. false
  104. )
  105. const variance = this.calculateVarianceForBitmap(scaledBitmap)
  106. totalVariance += variance
  107. scaleCount++
  108. if (!scaledBitmap.isRecycled()) {
  109. scaledBitmap.recycle()
  110. }
  111. }
  112. return scaleCount > 0 ? totalVariance / scaleCount : -1
  113. } catch (error) {
  114. console.error('多尺度清晰度计算失败:', error)
  115. return -1
  116. }
  117. }
  118. // 提取方差计算为独立方法
  119. private static calculateVarianceForBitmap(bitmap: Bitmap): number {
  120. const width = bitmap.getWidth()
  121. const height = bitmap.getHeight()
  122. let totalBrightness = 0.0
  123. let brightnessSquares = 0.0
  124. const pixelCount = width * height
  125. // 计算平均亮度
  126. for (let i: number = 0; i < width; i++) {
  127. for (let j: number = 0; j < height; j++) {
  128. const pixel = bitmap.getPixel(i.toInt(), j.toInt())
  129. const brightness = this.getPixelBrightness(pixel)
  130. totalBrightness += brightness
  131. }
  132. }
  133. const meanBrightness = totalBrightness / pixelCount
  134. // 计算亮度方差
  135. for (let i: number = 0; i < width; i++) {
  136. for (let j: number = 0; j < height; j++) {
  137. const pixel = bitmap.getPixel(i.toInt(), j.toInt())
  138. const brightness = this.getPixelBrightness(pixel)
  139. const diff = brightness - meanBrightness
  140. brightnessSquares += diff * diff
  141. }
  142. }
  143. return brightnessSquares / pixelCount
  144. }
  145. /**
  146. * 使用边缘密度作为清晰度指标(更适合大图)
  147. */
  148. static calculateEdgeDensity(bitmap: Bitmap): number {
  149. try {
  150. const width = bitmap.getWidth()
  151. const height = bitmap.getHeight()
  152. // 对于大图,使用适当的采样尺寸
  153. const sampleWidth = Math.min(width, 300)
  154. const sampleHeight = Math.min(height, (300 * height / width).toInt())
  155. const scaledBitmap = Bitmap.createScaledBitmap(
  156. bitmap,
  157. sampleWidth.toInt(),
  158. sampleHeight.toInt(),
  159. false
  160. )
  161. let edgeCount = 0
  162. const totalPixels = sampleWidth * sampleHeight
  163. // 简单的边缘检测
  164. for (let i: number = 1; i < sampleWidth - 1; i++) {
  165. for (let j: number = 1; j < sampleHeight - 1; j++) {
  166. const center = this.getPixelBrightness(scaledBitmap.getPixel(i.toInt(), j.toInt()))
  167. const right = this.getPixelBrightness(scaledBitmap.getPixel((i + 1).toInt(), j.toInt()))
  168. const bottom = this.getPixelBrightness(scaledBitmap.getPixel(i.toInt(), (j + 1).toInt()))
  169. // 如果相邻像素亮度差异大,认为是边缘
  170. if (Math.abs(center - right) > 20 || Math.abs(center - bottom) > 20) {
  171. edgeCount++
  172. }
  173. }
  174. }
  175. const edgeDensity = (edgeCount / totalPixels) * 1000
  176. if (!scaledBitmap.isRecycled()) {
  177. scaledBitmap.recycle()
  178. }
  179. return edgeDensity
  180. } catch (error) {
  181. console.error('边缘密度计算失败:', error)
  182. return -1
  183. }
  184. }
  185. /**
  186. * 获取像素亮度
  187. */
  188. private static getPixelBrightness(pixel: number): number {
  189. const r = (pixel >> 16) & 0xff
  190. const g = (pixel >> 8) & 0xff
  191. const b = pixel & 0xff
  192. return (r + g + b) / 3.0
  193. }
  194. /**
  195. * 判断图像是否清晰
  196. */
  197. static isImageClear(bitmap: Bitmap, threshold: number = 500): boolean {
  198. const variance = this.calculateBrightnessVariance2(bitmap)
  199. return variance > threshold
  200. }
  201. /**
  202. * 获取图像清晰度评分
  203. */
  204. static getImageClarityScore(bitmap: Bitmap): ImageClarityScore {
  205. const variance = this.calculateBrightnessVariance2(bitmap)
  206. let level: string
  207. if (variance < 0) {
  208. level = "处理失败"
  209. } else if (variance < 500) {
  210. level = "非常模糊"
  211. } else if (variance < 2000) {
  212. level = "模糊"
  213. } else if (variance < 3200) {
  214. level = "一般"
  215. } else if (variance < 3500) {
  216. level = "清晰"
  217. } else {
  218. level = "非常清晰"
  219. }
  220. return {
  221. score: Math.round(variance * 100) / 100,
  222. level: level
  223. }
  224. }
  225. /**
  226. * 从Base64数据检测清晰度
  227. */
  228. static checkImageClarityFromBase64(base64Data: string): ImageClarityResult {
  229. try {
  230. const bitmap = this.base64ToBitmap(base64Data)
  231. if (bitmap == null) {
  232. return { score: -1, level: "加载失败", isClear: false }
  233. }
  234. const clarityInfo = this.getImageClarityScore(bitmap)
  235. const isClear = this.isImageClear(bitmap)
  236. if (!bitmap.isRecycled()) {
  237. bitmap.recycle()
  238. }
  239. return {
  240. score: clarityInfo.score,
  241. level: clarityInfo.level,
  242. isClear: isClear
  243. }
  244. } catch (error) {
  245. console.error('图像清晰度检测失败:', error)
  246. return { score: -1, level: "检测失败", isClear: false }
  247. }
  248. }
  249. /**
  250. * 从文件路径检测清晰度
  251. */
  252. static checkImageClarityFromPath(imagePath: string): ImageClarityResult {
  253. try {
  254. const bitmap = BitmapFactory.decodeFile(imagePath)
  255. if (bitmap == null) {
  256. return { score: -1, level: "加载失败", isClear: false }
  257. }
  258. const clarityInfo = this.getImageClarityScore(bitmap)
  259. const isClear = this.isImageClear(bitmap)
  260. if (!bitmap.isRecycled()) {
  261. bitmap.recycle()
  262. }
  263. return {
  264. score: clarityInfo.score,
  265. level: clarityInfo.level,
  266. isClear: isClear
  267. }
  268. } catch (error) {
  269. console.error('图像清晰度检测失败:', error)
  270. return { score: -1, level: "检测失败", isClear: false }
  271. }
  272. }
  273. /**
  274. * 简化的Base64转Bitmap方法
  275. */
  276. private static base64ToBitmap(base64Data: string): Bitmap | null {
  277. try {
  278. // 移除Base64前缀
  279. let pureBase64 = base64Data
  280. if (base64Data.includes(',')) {
  281. pureBase64 = base64Data.split(',')[1]
  282. }
  283. // 解码Base64
  284. const decodedBytes = Base64.decode(pureBase64, Base64.DEFAULT)
  285. // 获取字节数组长度
  286. const bitmap = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size)
  287. return bitmap
  288. } catch (error) {
  289. console.error('Base64转Bitmap失败:', error)
  290. return null
  291. }
  292. }
  293. /**
  294. * 备用方法:直接返回清晰度分数(避免复杂对象类型)
  295. */
  296. static getSimpleClarityScore(bitmap: Bitmap): number {
  297. return this.calculateBrightnessVariance(bitmap)
  298. }
  299. /**
  300. * 从Base64获取简单清晰度分数
  301. */
  302. static getSimpleClarityScoreFromBase64(base64Data: string): number {
  303. try {
  304. const bitmap = this.base64ToBitmap(base64Data)
  305. if (bitmap == null) {
  306. return -1
  307. }
  308. const score = this.calculateBrightnessVariance(bitmap)
  309. if (!bitmap.isRecycled()) {
  310. bitmap.recycle()
  311. }
  312. return score
  313. } catch (error) {
  314. console.error('清晰度检测失败:', error)
  315. return -1
  316. }
  317. }
  318. }