mirror of
https://github.com/T8RIN/ImageToolbox.git
synced 2025-08-26 10:02:13 +08:00
Added ICO image conversion support
This commit is contained in:
@ -74,7 +74,8 @@ internal class AndroidImageCompressor @Inject constructor(
|
||||
SimpleCompressor
|
||||
.getInstance(
|
||||
imageFormat = imageFormat,
|
||||
context = context
|
||||
context = context,
|
||||
imageScaler = imageScaler
|
||||
)
|
||||
.compress(
|
||||
image = image.toSoftware(),
|
||||
|
@ -34,15 +34,19 @@ import com.radzivon.bartoshyk.avif.coder.AvifSpeed
|
||||
import com.radzivon.bartoshyk.avif.coder.HeifCoder
|
||||
import com.radzivon.bartoshyk.avif.coder.PreciseMode
|
||||
import com.t8rin.qoi_coder.QOIEncoder
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import org.beyka.tiffbitmapfactory.CompressionScheme
|
||||
import org.beyka.tiffbitmapfactory.Orientation
|
||||
import org.beyka.tiffbitmapfactory.TiffSaver
|
||||
import org.beyka.tiffbitmapfactory.TiffSaver.SaveOptions
|
||||
import ru.tech.imageresizershrinker.core.domain.image.ImageScaler
|
||||
import ru.tech.imageresizershrinker.core.domain.image.model.ImageFormat
|
||||
import ru.tech.imageresizershrinker.core.domain.image.model.Quality
|
||||
import ru.tech.imageresizershrinker.core.domain.image.model.ResizeType
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
|
||||
internal abstract class SimpleCompressor {
|
||||
@ -51,7 +55,8 @@ internal abstract class SimpleCompressor {
|
||||
|
||||
fun getInstance(
|
||||
imageFormat: ImageFormat,
|
||||
context: Context
|
||||
context: Context,
|
||||
imageScaler: ImageScaler<Bitmap>
|
||||
): SimpleCompressor = when (imageFormat) {
|
||||
ImageFormat.Avif.Lossless -> AvifLossless(context)
|
||||
ImageFormat.Avif.Lossy -> AvifLossy(context)
|
||||
@ -79,6 +84,8 @@ internal abstract class SimpleCompressor {
|
||||
ImageFormat.Tiff -> Tiff(context)
|
||||
|
||||
ImageFormat.Qoi -> Qoi
|
||||
|
||||
ImageFormat.Ico -> Ico(imageScaler)
|
||||
}
|
||||
|
||||
}
|
||||
@ -88,138 +95,6 @@ internal abstract class SimpleCompressor {
|
||||
quality: Quality
|
||||
): ByteArray
|
||||
|
||||
data object Bmp : SimpleCompressor() {
|
||||
private const val BMP_WIDTH_OF_TIMES = 4
|
||||
private const val BYTE_PER_PIXEL = 3
|
||||
|
||||
private fun writeInt(value: Int): ByteArray {
|
||||
val b = ByteArray(4)
|
||||
b[0] = (value and 0x000000FF).toByte()
|
||||
b[1] = (value and 0x0000FF00 shr 8).toByte()
|
||||
b[2] = (value and 0x00FF0000 shr 16).toByte()
|
||||
b[3] = (value and -0x1000000 shr 24).toByte()
|
||||
return b
|
||||
}
|
||||
|
||||
private fun writeShort(value: Short): ByteArray {
|
||||
val b = ByteArray(2)
|
||||
b[0] = (value.toInt() and 0x00FF).toByte()
|
||||
b[1] = (value.toInt() and 0xFF00 shr 8).toByte()
|
||||
return b
|
||||
}
|
||||
|
||||
override suspend fun compress(
|
||||
image: Bitmap,
|
||||
quality: Quality
|
||||
): ByteArray {
|
||||
//image size
|
||||
val width = image.width
|
||||
val height = image.height
|
||||
|
||||
//image dummy data size
|
||||
//reason : the amount of bytes per image row must be a multiple of 4 (requirements of bmp format)
|
||||
var dummyBytesPerRow: ByteArray? = null
|
||||
var hasDummy = false
|
||||
val rowWidthInBytes =
|
||||
BYTE_PER_PIXEL * width //source image width * number of bytes to encode one pixel.
|
||||
if (rowWidthInBytes % BMP_WIDTH_OF_TIMES > 0) {
|
||||
hasDummy = true
|
||||
//the number of dummy bytes we need to add on each row
|
||||
dummyBytesPerRow =
|
||||
ByteArray(BMP_WIDTH_OF_TIMES - rowWidthInBytes % BMP_WIDTH_OF_TIMES)
|
||||
//just fill an array with the dummy bytes we need to append at the end of each row
|
||||
for (i in dummyBytesPerRow.indices) {
|
||||
dummyBytesPerRow[i] = 0xFF.toByte()
|
||||
}
|
||||
}
|
||||
|
||||
//an array to receive the pixels from the source image
|
||||
val pixels = IntArray(width * height)
|
||||
|
||||
//the number of bytes used in the file to store raw image data (excluding file headers)
|
||||
val imageSize =
|
||||
(rowWidthInBytes + if (hasDummy) dummyBytesPerRow!!.size else 0) * height
|
||||
//file headers size
|
||||
val imageDataOffset = 0x36
|
||||
|
||||
//final size of the file
|
||||
val fileSize = imageSize + imageDataOffset
|
||||
|
||||
//Android Bitmap Image Data
|
||||
image.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
|
||||
//ByteArrayOutputStream baos = new ByteArrayOutputStream(fileSize);
|
||||
val buffer = ByteBuffer.allocate(fileSize)
|
||||
/**
|
||||
* BITMAP FILE HEADER Write Start
|
||||
*/
|
||||
buffer.put(0x42.toByte())
|
||||
buffer.put(0x4D.toByte())
|
||||
|
||||
//size
|
||||
buffer.put(writeInt(fileSize))
|
||||
|
||||
//reserved
|
||||
buffer.put(writeShort(0.toShort()))
|
||||
buffer.put(writeShort(0.toShort()))
|
||||
|
||||
//image data start offset
|
||||
buffer.put(writeInt(imageDataOffset))
|
||||
/** BITMAP FILE HEADER Write End */
|
||||
|
||||
//*******************************************
|
||||
/** BITMAP INFO HEADER Write Start */
|
||||
//size
|
||||
buffer.put(writeInt(0x28))
|
||||
|
||||
//width, height
|
||||
//if we add 3 dummy bytes per row : it means we add a pixel (and the image width is modified.
|
||||
buffer.put(writeInt(width + if (hasDummy) (if (dummyBytesPerRow!!.size == 3) 1 else 0) else 0))
|
||||
buffer.put(writeInt(height))
|
||||
|
||||
//planes
|
||||
buffer.put(writeShort(1.toShort()))
|
||||
|
||||
//bit count
|
||||
buffer.put(writeShort(24.toShort()))
|
||||
|
||||
//bit compression
|
||||
buffer.put(writeInt(0))
|
||||
|
||||
//image data size
|
||||
buffer.put(writeInt(imageSize))
|
||||
|
||||
//horizontal resolution in pixels per meter
|
||||
buffer.put(writeInt(0))
|
||||
|
||||
//vertical resolution in pixels per meter (unreliable)
|
||||
buffer.put(writeInt(0))
|
||||
buffer.put(writeInt(0))
|
||||
buffer.put(writeInt(0))
|
||||
/** BITMAP INFO HEADER Write End */
|
||||
var row = height
|
||||
var startPosition = (row - 1) * width
|
||||
var endPosition = row * width
|
||||
while (row > 0) {
|
||||
for (i in startPosition until endPosition) {
|
||||
buffer.put((pixels[i] and 0x000000FF).toByte())
|
||||
buffer.put((pixels[i] and 0x0000FF00 shr 8).toByte())
|
||||
buffer.put((pixels[i] and 0x00FF0000 shr 16).toByte())
|
||||
}
|
||||
if (hasDummy) {
|
||||
if (dummyBytesPerRow != null) {
|
||||
buffer.put(dummyBytesPerRow)
|
||||
}
|
||||
}
|
||||
row--
|
||||
endPosition = startPosition
|
||||
startPosition -= width
|
||||
}
|
||||
return buffer.array()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data object Jpg : SimpleCompressor() {
|
||||
|
||||
override suspend fun compress(
|
||||
@ -523,4 +398,217 @@ internal abstract class SimpleCompressor {
|
||||
|
||||
}
|
||||
|
||||
data class Ico(
|
||||
private val imageScaler: ImageScaler<Bitmap>
|
||||
) : SimpleCompressor() {
|
||||
|
||||
override suspend fun compress(
|
||||
image: Bitmap,
|
||||
quality: Quality
|
||||
): ByteArray = coroutineScope {
|
||||
val bitmap = if (image.width > 256 || image.height > 256) {
|
||||
imageScaler.scaleImage(
|
||||
image = image,
|
||||
width = 256,
|
||||
height = 256,
|
||||
resizeType = ResizeType.Flexible
|
||||
)
|
||||
} else image
|
||||
|
||||
val width = bitmap.width
|
||||
val height = bitmap.height
|
||||
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
val header = ByteArray(6)
|
||||
val entry = ByteArray(16)
|
||||
val infoHeader = ByteArray(40)
|
||||
|
||||
// ICO Header
|
||||
header[2] = 1 // Image type: Icon
|
||||
header[4] = 1 // Number of images
|
||||
|
||||
outputStream.write(header)
|
||||
|
||||
// Image entry
|
||||
entry[0] = if (width > 256) 0 else width.toByte()
|
||||
entry[1] = if (height > 256) 0 else height.toByte()
|
||||
entry[4] = 1 // Color planes
|
||||
entry[6] = 32 // Bits per pixel
|
||||
|
||||
val andMaskSize = ((width + 31) / 32) * 4 * height
|
||||
val xorMaskSize = width * height * 4
|
||||
val imageDataSize = infoHeader.size + xorMaskSize + andMaskSize
|
||||
|
||||
ByteBuffer.wrap(entry, 8, 4).order(ByteOrder.LITTLE_ENDIAN).putInt(imageDataSize)
|
||||
ByteBuffer.wrap(entry, 12, 4)
|
||||
.order(ByteOrder.LITTLE_ENDIAN)
|
||||
.putInt(header.size + entry.size)
|
||||
|
||||
outputStream.write(entry)
|
||||
|
||||
// BITMAP INFO HEADER
|
||||
ByteBuffer.wrap(infoHeader).order(ByteOrder.LITTLE_ENDIAN).apply {
|
||||
putInt(40) // Header size
|
||||
putInt(width) // Width
|
||||
putInt(height * 2) // Height (XOR + AND masks)
|
||||
putShort(1) // Color planes
|
||||
putShort(32) // Bits per pixel
|
||||
putInt(0) // Compression (BI_RGB)
|
||||
putInt(xorMaskSize + andMaskSize) // Image size
|
||||
}
|
||||
|
||||
outputStream.write(infoHeader)
|
||||
|
||||
// XOR mask (pixel data)
|
||||
for (y in height - 1 downTo 0) {
|
||||
for (x in 0 until width) {
|
||||
val pixel = bitmap.getPixel(x, y)
|
||||
outputStream.write(pixel and 0xFF) // B
|
||||
outputStream.write((pixel shr 8) and 0xFF) // G
|
||||
outputStream.write((pixel shr 16) and 0xFF) // R
|
||||
outputStream.write((pixel shr 24) and 0xFF) // A
|
||||
}
|
||||
}
|
||||
|
||||
// AND mask (all 0 for no transparency mask)
|
||||
outputStream.write(ByteArray(andMaskSize))
|
||||
|
||||
outputStream.toByteArray()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data object Bmp : SimpleCompressor() {
|
||||
|
||||
private const val BMP_WIDTH_OF_TIMES = 4
|
||||
private const val BYTE_PER_PIXEL = 3
|
||||
|
||||
private fun writeInt(value: Int): ByteArray {
|
||||
val b = ByteArray(4)
|
||||
b[0] = (value and 0x000000FF).toByte()
|
||||
b[1] = (value and 0x0000FF00 shr 8).toByte()
|
||||
b[2] = (value and 0x00FF0000 shr 16).toByte()
|
||||
b[3] = (value and -0x1000000 shr 24).toByte()
|
||||
return b
|
||||
}
|
||||
|
||||
private fun writeShort(value: Short): ByteArray {
|
||||
val b = ByteArray(2)
|
||||
b[0] = (value.toInt() and 0x00FF).toByte()
|
||||
b[1] = (value.toInt() and 0xFF00 shr 8).toByte()
|
||||
return b
|
||||
}
|
||||
|
||||
override suspend fun compress(
|
||||
image: Bitmap,
|
||||
quality: Quality
|
||||
): ByteArray {
|
||||
//image size
|
||||
val width = image.width
|
||||
val height = image.height
|
||||
|
||||
//image dummy data size
|
||||
//reason : the amount of bytes per image row must be a multiple of 4 (requirements of bmp format)
|
||||
var dummyBytesPerRow: ByteArray? = null
|
||||
var hasDummy = false
|
||||
val rowWidthInBytes =
|
||||
BYTE_PER_PIXEL * width //source image width * number of bytes to encode one pixel.
|
||||
if (rowWidthInBytes % BMP_WIDTH_OF_TIMES > 0) {
|
||||
hasDummy = true
|
||||
//the number of dummy bytes we need to add on each row
|
||||
dummyBytesPerRow =
|
||||
ByteArray(BMP_WIDTH_OF_TIMES - rowWidthInBytes % BMP_WIDTH_OF_TIMES)
|
||||
//just fill an array with the dummy bytes we need to append at the end of each row
|
||||
for (i in dummyBytesPerRow.indices) {
|
||||
dummyBytesPerRow[i] = 0xFF.toByte()
|
||||
}
|
||||
}
|
||||
|
||||
//an array to receive the pixels from the source image
|
||||
val pixels = IntArray(width * height)
|
||||
|
||||
//the number of bytes used in the file to store raw image data (excluding file headers)
|
||||
val imageSize =
|
||||
(rowWidthInBytes + if (hasDummy) dummyBytesPerRow!!.size else 0) * height
|
||||
//file headers size
|
||||
val imageDataOffset = 0x36
|
||||
|
||||
//final size of the file
|
||||
val fileSize = imageSize + imageDataOffset
|
||||
|
||||
//Android Bitmap Image Data
|
||||
image.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
|
||||
//ByteArrayOutputStream baos = new ByteArrayOutputStream(fileSize);
|
||||
val buffer = ByteBuffer.allocate(fileSize)
|
||||
/**
|
||||
* BITMAP FILE HEADER Write Start
|
||||
*/
|
||||
buffer.put(0x42.toByte())
|
||||
buffer.put(0x4D.toByte())
|
||||
|
||||
//size
|
||||
buffer.put(writeInt(fileSize))
|
||||
|
||||
//reserved
|
||||
buffer.put(writeShort(0.toShort()))
|
||||
buffer.put(writeShort(0.toShort()))
|
||||
|
||||
//image data start offset
|
||||
buffer.put(writeInt(imageDataOffset))
|
||||
/** BITMAP FILE HEADER Write End */
|
||||
|
||||
//*******************************************
|
||||
/** BITMAP INFO HEADER Write Start */
|
||||
//size
|
||||
buffer.put(writeInt(0x28))
|
||||
|
||||
//width, height
|
||||
//if we add 3 dummy bytes per row : it means we add a pixel (and the image width is modified.
|
||||
buffer.put(writeInt(width + if (hasDummy) (if (dummyBytesPerRow!!.size == 3) 1 else 0) else 0))
|
||||
buffer.put(writeInt(height))
|
||||
|
||||
//planes
|
||||
buffer.put(writeShort(1.toShort()))
|
||||
|
||||
//bit count
|
||||
buffer.put(writeShort(24.toShort()))
|
||||
|
||||
//bit compression
|
||||
buffer.put(writeInt(0))
|
||||
|
||||
//image data size
|
||||
buffer.put(writeInt(imageSize))
|
||||
|
||||
//horizontal resolution in pixels per meter
|
||||
buffer.put(writeInt(0))
|
||||
|
||||
//vertical resolution in pixels per meter (unreliable)
|
||||
buffer.put(writeInt(0))
|
||||
buffer.put(writeInt(0))
|
||||
buffer.put(writeInt(0))
|
||||
/** BITMAP INFO HEADER Write End */
|
||||
var row = height
|
||||
var startPosition = (row - 1) * width
|
||||
var endPosition = row * width
|
||||
while (row > 0) {
|
||||
for (i in startPosition until endPosition) {
|
||||
buffer.put((pixels[i] and 0x000000FF).toByte())
|
||||
buffer.put((pixels[i] and 0x0000FF00 shr 8).toByte())
|
||||
buffer.put((pixels[i] and 0x00FF0000 shr 16).toByte())
|
||||
}
|
||||
if (hasDummy) {
|
||||
if (dummyBytesPerRow != null) {
|
||||
buffer.put(dummyBytesPerRow)
|
||||
}
|
||||
}
|
||||
row--
|
||||
endPosition = startPosition
|
||||
startPosition -= width
|
||||
}
|
||||
return buffer.array()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -254,6 +254,13 @@ sealed class ImageFormat(
|
||||
canChangeCompressionValue = false
|
||||
)
|
||||
|
||||
data object Ico : ImageFormat(
|
||||
title = "ICO",
|
||||
extension = "ico",
|
||||
mimeType = "image/x-icon",
|
||||
canChangeCompressionValue = false
|
||||
)
|
||||
|
||||
companion object {
|
||||
sealed class CompressionType(
|
||||
open val compressionRange: IntRange = 0..100
|
||||
@ -284,6 +291,8 @@ sealed class ImageFormat(
|
||||
typeString.contains("avif") -> Avif.Lossless
|
||||
typeString.contains("heif") -> Heif.Lossless
|
||||
typeString.contains("heic") -> Heic.Lossless
|
||||
typeString.contains("qoi") -> Qoi
|
||||
typeString.contains("ico") -> Ico
|
||||
else -> Default
|
||||
}
|
||||
|
||||
@ -320,7 +329,9 @@ sealed class ImageFormat(
|
||||
Tif,
|
||||
Tiff,
|
||||
Jpeg2000.Jp2,
|
||||
Jpeg2000.J2k
|
||||
Jpeg2000.J2k,
|
||||
Qoi,
|
||||
Ico
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +103,13 @@ sealed class ImageFormatGroup(
|
||||
)
|
||||
)
|
||||
|
||||
data object Ico : ImageFormatGroup(
|
||||
title = "ICO",
|
||||
formats = listOf(
|
||||
ImageFormat.Ico
|
||||
)
|
||||
)
|
||||
|
||||
data class Custom(
|
||||
override val title: String,
|
||||
override val formats: List<ImageFormat>
|
||||
@ -110,7 +117,7 @@ sealed class ImageFormatGroup(
|
||||
|
||||
companion object {
|
||||
val entries by lazy {
|
||||
listOf(Jpg, Png, Webp, Avif, Heic, Jxl, Bmp, Jpeg2000, Tiff, Qoi)
|
||||
listOf(Jpg, Png, Webp, Avif, Heic, Jxl, Bmp, Jpeg2000, Tiff, Qoi, Ico)
|
||||
}
|
||||
|
||||
val alphaContainedEntries
|
||||
@ -121,7 +128,8 @@ sealed class ImageFormatGroup(
|
||||
Heic,
|
||||
Jxl,
|
||||
Jpeg2000,
|
||||
Qoi
|
||||
Qoi,
|
||||
Ico
|
||||
)
|
||||
|
||||
val highLevelFormats by lazy {
|
||||
|
@ -1347,4 +1347,5 @@
|
||||
<string name="links_preview">Links Preview</string>
|
||||
<string name="links_preview_sub">Enables link preview retrieveing in places where you can obtain text (QRCode, OCR etc)</string>
|
||||
<string name="links">Links</string>
|
||||
<string name="ico_size_warning">Ico Files can be saved only at maximum size of 256*256, larger values will be coerced in resulting image</string>
|
||||
</resources>
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* ImageToolbox is an image editor for android
|
||||
* Copyright (c) 2024 T8RIN (Malik Mukhametzyanov)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* You should have received a copy of the Apache License
|
||||
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||
*/
|
||||
|
||||
package ru.tech.imageresizershrinker.core.ui.widget.controls
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import ru.tech.imageresizershrinker.core.resources.R
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.modifier.container
|
||||
|
||||
@Composable
|
||||
fun IcoSizeWarning(visible: Boolean) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = 12.dp
|
||||
)
|
||||
.container(
|
||||
color = MaterialTheme.colorScheme.secondaryContainer.copy(
|
||||
alpha = 0.7f
|
||||
),
|
||||
resultPadding = 4.dp,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.ico_size_warning),
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.padding(8.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.5f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -46,7 +46,9 @@ fun OOMWarning(visible: Boolean) {
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(12.dp)
|
||||
.padding(
|
||||
top = 12.dp
|
||||
)
|
||||
.container(
|
||||
color = MaterialTheme.colorScheme.errorContainer.copy(
|
||||
alpha = 0.7f
|
||||
|
@ -31,6 +31,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ru.tech.imageresizershrinker.core.domain.image.model.ImageFormat
|
||||
import ru.tech.imageresizershrinker.core.domain.image.model.ImageInfo
|
||||
import ru.tech.imageresizershrinker.core.domain.model.IntegerSize
|
||||
import ru.tech.imageresizershrinker.core.resources.R
|
||||
@ -125,7 +126,11 @@ fun ResizeImageField(
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
|
||||
IcoSizeWarning(
|
||||
visible = imageInfo.run {
|
||||
imageFormat == ImageFormat.Ico && (width > 256 || height > 256)
|
||||
}
|
||||
)
|
||||
OOMWarning(visible = showWarning)
|
||||
}
|
||||
}
|
@ -3,8 +3,8 @@ androidMinSdk = "21"
|
||||
androidTargetSdk = "34"
|
||||
androidCompileSdk = "34"
|
||||
|
||||
versionName = "2.9.1-rc01"
|
||||
versionCode = "150"
|
||||
versionName = "2.9.1-rc02"
|
||||
versionCode = "151"
|
||||
|
||||
jvmTarget = "17"
|
||||
compose-compiler = "1.5.15"
|
||||
|
Reference in New Issue
Block a user