Android 位图:缩放时像素显示为矩形(可能是位图伪像)
Android Bitmap: Pixels appear to be rectangular when zooming (possible Bitmap artifacts)
我正在为 Android 创建一个像素艺术编辑器,为此,我使用了带有位图的 Canvas。
这是我的一些代码 (MyCanvasView
) 的一部分,它处理大部分像素艺术功能:
package com.realtomjoney.pyxlmoose.customviews.mycanvasview
import android.content.Context
import android.view.MotionEvent
import android.view.View
import androidx.lifecycle.LifecycleOwner
import com.realtomjoney.pyxlmoose.listeners.CanvasFragmentListener
import com.realtomjoney.pyxlmoose.models.BitmapAction
import com.realtomjoney.pyxlmoose.models.XYPosition
import android.graphics.*
import com.realtomjoney.pyxlmoose.activities.canvas.canvasInstance
import com.realtomjoney.pyxlmoose.models.BitmapActionData
class MyCanvasView (context: Context, private var spanCount: Int) : View(context) {
lateinit var extraCanvas: Canvas
lateinit var extraBitmap: Bitmap
private var scaleWidth = 0f
private var scaleHeight = 0f
var prevX: Int? = null
var prevY: Int? = null
val bitmapActionData: MutableList<BitmapAction> = mutableListOf()
var currentBitmapAction: BitmapAction? = null
var lifecycleOwner: LifecycleOwner? = null
private lateinit var caller: CanvasFragmentListener
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
caller = context as CanvasFragmentListener
if (::extraBitmap.isInitialized) extraBitmap.recycle()
extraBitmap = Bitmap.createBitmap(spanCount, spanCount, Bitmap.Config.ARGB_8888)
extraCanvas = Canvas(extraBitmap)
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
val coordinateX = (event.x / scaleWidth).toInt()
val coordinateY = (event.y / scaleWidth).toInt()
if (currentBitmapAction == null) {
currentBitmapAction = BitmapAction(mutableListOf())
}
when (event.actionMasked) {
MotionEvent.ACTION_MOVE -> {
if (coordinateX in 0 until spanCount && coordinateY in 0 until spanCount) {
caller.onPixelTapped(extraBitmap, XYPosition(coordinateX, coordinateY))
} else {
prevX = null
prevY = null
}
}
MotionEvent.ACTION_DOWN -> {
if (coordinateX in 0 until spanCount && coordinateY in 0 until spanCount) {
caller.onPixelTapped(extraBitmap, XYPosition(coordinateX, coordinateY))
} else {
prevX = null
prevY = null
}
}
MotionEvent.ACTION_UP -> {
caller.onActionUp()
}
}
invalidate()
return true
}
fun undo() {
if (bitmapActionData.size > 0) {
if (!bitmapActionData.last().isFilterBased) {
for ((key, value) in bitmapActionData.last().actionData.distinctBy { it.xyPosition }) {
extraBitmap.setPixel(key.x, key.y, value)
}
} else {
for ((key, value) in bitmapActionData.last().actionData) {
extraBitmap.setPixel(key.x, key.y, value)
}
}
invalidate()
bitmapActionData.removeLast()
}
}
fun clearCanvas() {
for (i_1 in 0 until extraBitmap.width) {
for (i_2 in 0 until extraBitmap.height) {
extraBitmap.setPixel(i_1, i_2, Color.TRANSPARENT)
}
}
invalidate()
bitmapActionData.clear()
}
private fun getResizedBitmap(bm: Bitmap, newHeight: Int, newWidth: Int): Bitmap? {
val width = bm.width
val height = bm.height
val scaleWidth = newWidth.toFloat() / width
val scaleHeight = newHeight.toFloat() / height
this.scaleWidth = scaleWidth
this.scaleHeight = scaleHeight
val matrix = Matrix()
matrix.postScale(scaleWidth, scaleHeight)
return Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false)
}
override fun onDraw(canvas: Canvas) {
canvas.drawBitmap(getResizedBitmap(extraBitmap, this.width, this.width)!!, 0f, 0f, null)
}
}
('MyCanvasView' 然后被加载到位于 CanvasActivity
内的片段中。)
像素可以简单地使用 Bitmap.setPixel
方法设置,如果您没有从该代码中看到的话。并且你可能已经看到像素点之间通过直线算法连接在一起,给用户一种无限硬件输入速率的错觉。
我似乎遇到了一个很奇怪的问题(这是最严重的错误,因为我的应用程序还有许多其他错误以及这个错误)。
假设我创建了一个跨度数约为 100 的 canvas,并使用铅笔工具绘制了一些东西:
正如您从图片中看到的那样 - 放大时像素呈现良好,它们看起来是一个完美的正方形。
现在,假设我创建了一个跨度数约为 670 的 canvas(请注意,这是一种罕见的边缘情况,但一切仍需要正常运行 - 即使对于较大的位图也是如此)并绘制一些东西:
从外面看还算不错,但放大后:
..像素点呈矩形,整体看起来很奇怪
对于超过 1500x1500 的 canvas 尺寸(是的,我知道,这是一种非常罕见的边缘情况),伪像更加明显,每个像素之间甚至出现空格:
我已经和一些有像素艺术编辑经验的人谈过,他们无法告诉我为什么会发生这种情况 - 但他们认为这与 getResizedBitmap
方法有关 -虽然我不完全确定这是不是真的。
这个问题本身并不严重 - 因为它是一个移动编辑器,大多数用户不会使用 canvas 670x670 大小,但我认为它仍然值得修复。大多数事情我都是按照书做的,所以我很困惑为什么会出现这些工件。
如果有人知道这个问题的主要原因是什么或者能以任何方式帮助我,我将不胜感激!
为什么要调整位图的大小?为什么每次调用 onDraw
时都创建一个新位图?
每次调整位图大小时,都会应用放大或缩小算法。这可能是您看到的失真的原因。
您应该在开始时创建一个位图,并在 onDraw 方法中渲染它。您在 getResizedBitmap
中计算的矩阵应该用作 this function
的参数
我正在为 Android 创建一个像素艺术编辑器,为此,我使用了带有位图的 Canvas。
这是我的一些代码 (MyCanvasView
) 的一部分,它处理大部分像素艺术功能:
package com.realtomjoney.pyxlmoose.customviews.mycanvasview
import android.content.Context
import android.view.MotionEvent
import android.view.View
import androidx.lifecycle.LifecycleOwner
import com.realtomjoney.pyxlmoose.listeners.CanvasFragmentListener
import com.realtomjoney.pyxlmoose.models.BitmapAction
import com.realtomjoney.pyxlmoose.models.XYPosition
import android.graphics.*
import com.realtomjoney.pyxlmoose.activities.canvas.canvasInstance
import com.realtomjoney.pyxlmoose.models.BitmapActionData
class MyCanvasView (context: Context, private var spanCount: Int) : View(context) {
lateinit var extraCanvas: Canvas
lateinit var extraBitmap: Bitmap
private var scaleWidth = 0f
private var scaleHeight = 0f
var prevX: Int? = null
var prevY: Int? = null
val bitmapActionData: MutableList<BitmapAction> = mutableListOf()
var currentBitmapAction: BitmapAction? = null
var lifecycleOwner: LifecycleOwner? = null
private lateinit var caller: CanvasFragmentListener
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
caller = context as CanvasFragmentListener
if (::extraBitmap.isInitialized) extraBitmap.recycle()
extraBitmap = Bitmap.createBitmap(spanCount, spanCount, Bitmap.Config.ARGB_8888)
extraCanvas = Canvas(extraBitmap)
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
val coordinateX = (event.x / scaleWidth).toInt()
val coordinateY = (event.y / scaleWidth).toInt()
if (currentBitmapAction == null) {
currentBitmapAction = BitmapAction(mutableListOf())
}
when (event.actionMasked) {
MotionEvent.ACTION_MOVE -> {
if (coordinateX in 0 until spanCount && coordinateY in 0 until spanCount) {
caller.onPixelTapped(extraBitmap, XYPosition(coordinateX, coordinateY))
} else {
prevX = null
prevY = null
}
}
MotionEvent.ACTION_DOWN -> {
if (coordinateX in 0 until spanCount && coordinateY in 0 until spanCount) {
caller.onPixelTapped(extraBitmap, XYPosition(coordinateX, coordinateY))
} else {
prevX = null
prevY = null
}
}
MotionEvent.ACTION_UP -> {
caller.onActionUp()
}
}
invalidate()
return true
}
fun undo() {
if (bitmapActionData.size > 0) {
if (!bitmapActionData.last().isFilterBased) {
for ((key, value) in bitmapActionData.last().actionData.distinctBy { it.xyPosition }) {
extraBitmap.setPixel(key.x, key.y, value)
}
} else {
for ((key, value) in bitmapActionData.last().actionData) {
extraBitmap.setPixel(key.x, key.y, value)
}
}
invalidate()
bitmapActionData.removeLast()
}
}
fun clearCanvas() {
for (i_1 in 0 until extraBitmap.width) {
for (i_2 in 0 until extraBitmap.height) {
extraBitmap.setPixel(i_1, i_2, Color.TRANSPARENT)
}
}
invalidate()
bitmapActionData.clear()
}
private fun getResizedBitmap(bm: Bitmap, newHeight: Int, newWidth: Int): Bitmap? {
val width = bm.width
val height = bm.height
val scaleWidth = newWidth.toFloat() / width
val scaleHeight = newHeight.toFloat() / height
this.scaleWidth = scaleWidth
this.scaleHeight = scaleHeight
val matrix = Matrix()
matrix.postScale(scaleWidth, scaleHeight)
return Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false)
}
override fun onDraw(canvas: Canvas) {
canvas.drawBitmap(getResizedBitmap(extraBitmap, this.width, this.width)!!, 0f, 0f, null)
}
}
('MyCanvasView' 然后被加载到位于 CanvasActivity
内的片段中。)
像素可以简单地使用 Bitmap.setPixel
方法设置,如果您没有从该代码中看到的话。并且你可能已经看到像素点之间通过直线算法连接在一起,给用户一种无限硬件输入速率的错觉。
我似乎遇到了一个很奇怪的问题(这是最严重的错误,因为我的应用程序还有许多其他错误以及这个错误)。
假设我创建了一个跨度数约为 100 的 canvas,并使用铅笔工具绘制了一些东西:
正如您从图片中看到的那样 - 放大时像素呈现良好,它们看起来是一个完美的正方形。
现在,假设我创建了一个跨度数约为 670 的 canvas(请注意,这是一种罕见的边缘情况,但一切仍需要正常运行 - 即使对于较大的位图也是如此)并绘制一些东西:
从外面看还算不错,但放大后:
..像素点呈矩形,整体看起来很奇怪
对于超过 1500x1500 的 canvas 尺寸(是的,我知道,这是一种非常罕见的边缘情况),伪像更加明显,每个像素之间甚至出现空格:
我已经和一些有像素艺术编辑经验的人谈过,他们无法告诉我为什么会发生这种情况 - 但他们认为这与 getResizedBitmap
方法有关 -虽然我不完全确定这是不是真的。
这个问题本身并不严重 - 因为它是一个移动编辑器,大多数用户不会使用 canvas 670x670 大小,但我认为它仍然值得修复。大多数事情我都是按照书做的,所以我很困惑为什么会出现这些工件。
如果有人知道这个问题的主要原因是什么或者能以任何方式帮助我,我将不胜感激!
为什么要调整位图的大小?为什么每次调用 onDraw
时都创建一个新位图?
每次调整位图大小时,都会应用放大或缩小算法。这可能是您看到的失真的原因。
您应该在开始时创建一个位图,并在 onDraw 方法中渲染它。您在 getResizedBitmap
中计算的矩阵应该用作 this function