|
1 | 1 | package com.d4rk.cleaner.app.images.utils |
2 | 2 |
|
| 3 | +import android.graphics.Bitmap |
3 | 4 | import android.graphics.BitmapFactory |
4 | 5 | import androidx.core.graphics.scale |
5 | 6 | import java.io.File |
6 | 7 |
|
7 | 8 | object ImageHashUtils { |
8 | 9 | fun perceptualHash(file: File): String? = runCatching { |
9 | | - val bitmap = BitmapFactory.decodeFile(file.absolutePath) ?: return null |
10 | | - val resized = bitmap.scale(8, 8) |
11 | | - val pixels = IntArray(64) |
12 | | - resized.getPixels(pixels, 0, 8, 0, 0, 8, 8) |
13 | | - var sum = 0 |
14 | | - val gray = IntArray(64) |
15 | | - pixels.forEachIndexed { index, pixel -> |
16 | | - val r = (pixel shr 16) and 0xff |
17 | | - val g = (pixel shr 8) and 0xff |
18 | | - val b = pixel and 0xff |
19 | | - val lum = (r + g + b) / 3 |
20 | | - gray[index] = lum |
21 | | - sum += lum |
22 | | - } |
23 | | - val avg = sum / 64 |
24 | | - var hash = 0L |
25 | | - gray.forEachIndexed { index, lum -> |
26 | | - if (lum >= avg) { |
27 | | - hash = hash or (1L shl (63 - index)) |
| 10 | + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } |
| 11 | + BitmapFactory.decodeFile(file.absolutePath, options) |
| 12 | + options.inSampleSize = calculateInSampleSize(options, 8, 8) |
| 13 | + options.inJustDecodeBounds = false |
| 14 | + BitmapFactory.decodeFile(file.absolutePath, options)?.use { bitmap -> |
| 15 | + bitmap.scale(8, 8).use { resized -> |
| 16 | + val pixels = IntArray(64) |
| 17 | + resized.getPixels(pixels, 0, 8, 0, 0, 8, 8) |
| 18 | + var sum = 0 |
| 19 | + val gray = IntArray(64) |
| 20 | + pixels.forEachIndexed { index, pixel -> |
| 21 | + val r = (pixel shr 16) and 0xff |
| 22 | + val g = (pixel shr 8) and 0xff |
| 23 | + val b = pixel and 0xff |
| 24 | + val lum = (r + g + b) / 3 |
| 25 | + gray[index] = lum |
| 26 | + sum += lum |
| 27 | + } |
| 28 | + val avg = sum / 64 |
| 29 | + var hash = 0L |
| 30 | + gray.forEachIndexed { index, lum -> |
| 31 | + if (lum >= avg) { |
| 32 | + hash = hash or (1L shl (63 - index)) |
| 33 | + } |
| 34 | + } |
| 35 | + java.lang.Long.toHexString(hash) |
28 | 36 | } |
29 | 37 | } |
30 | | - resized.recycle() |
31 | | - bitmap.recycle() |
32 | | - java.lang.Long.toHexString(hash) |
33 | 38 | }.getOrNull() |
| 39 | + |
| 40 | + private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { |
| 41 | + val (height, width) = options.run { outHeight to outWidth } |
| 42 | + var inSampleSize = 1 |
| 43 | + if (height > reqHeight || width > reqWidth) { |
| 44 | + var halfHeight = height / 2 |
| 45 | + var halfWidth = width / 2 |
| 46 | + while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { |
| 47 | + inSampleSize *= 2 |
| 48 | + } |
| 49 | + } |
| 50 | + return inSampleSize |
| 51 | + } |
| 52 | + |
| 53 | + private inline fun <T> Bitmap.use(block: (Bitmap) -> T): T { |
| 54 | + return try { |
| 55 | + block(this) |
| 56 | + } finally { |
| 57 | + recycle() |
| 58 | + } |
| 59 | + } |
34 | 60 | } |
35 | 61 |
|
0 commit comments