Как создать маску для прозрачного наложения?
У меня есть следующий сценарий: растровое изображение, которое используется в качестве фона, и другое растровое изображение, которое используется в качестве наложения, которое может быть прозрачным на 50% или непрозрачным (изменяемым во время выполнения) и третье растровое изображение, которое содержит маску для этого второго растрового изображения. Я пробовал разные конфигурации Xfermodes и порядок рисования, но не смог найти правильную.
Я использую маску как растровое изображение, потому что мне нужно сохранить ее между двумя запусками программы или между изменениями конфигурации. Он создается по мере того, как пользователь рисует на экране, эффективно убирая туман войны.
Отрывки кода из наилучшей попытки. Единственное, что не сработало, как мне хотелось, - это прозрачность моей маски.
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mFogOfWar, mTransformationMatrix, mPaintFog);
canvas.drawBitmap(mMaskBitmap, mTransformationMatrix, mPaintMask);
canvas.drawBitmap(mImage, mTransformationMatrix, mPaintImage);
}
Paint
объекты:
mPaintImage.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
mPaintFog.setAlpha(127);
mPaintMask.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
Вот что я получаю с текущей конфигурацией, чтобы быть более понятным:
Я не уверен, смогу ли я сделать эту настройку альфа на объекте Paint; если нет, то я не возражаю против другого предложения или решения проблемы альфа, предпочтительно такого, в котором воссоздание растрового изображения, используемого в качестве тумана войны, не является необходимым.
РЕДАКТИРОВАТЬ:
Я смог получить желаемые результаты, выполнив следующие действия:
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mImage, mTransformationMatrix, mPaintImage);
if (mMaskBitmap != null) {
canvas.drawBitmap(mFogOfWar, mTransformationMatrix, mPaintFog);
canvas.drawBitmap(mMaskBitmap, mTransformationMatrix, mPaintMask);
canvas.drawBitmap(mMaskBitmap, mTransformationMatrix, mPaintImage);
canvas.drawBitmap(mImage, mTransformationMatrix, mPaintImageSecondPass);
}
Paint
Объекты:
mPaintImage = new Paint(); // No Xfermode here anymore
mPaintFog.setAlpha(127);
mPaintMask.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mPaintImageSecondPass.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN));
Но рисование растровых изображений пять раз кажется пустой тратой. Поскольку это работает в текстуре OpenGL из-за аппаратного ускорения Android (я масштабирую растровые изображения до самого высокого разрешения, принимаемого графическим процессором устройства), и я очень стараюсь invalidates()
он работает на моем Nexus S и A500 на удивление гладко, но я не уверен насчет других устройств (в любом случае проект будет 4.0+).
Но я убежден, что должен быть лучший способ сделать это. Я бы хотел, чтобы этот способ избежал слишком большого перерасхода или, по крайней мере, мог бы правильно объяснить мне, что означают эти Xfermodes, и что я не перезаписываю вещи.
1 ответ
Я попробовал совершенно другой подход после того, как получил какое-то прозрение - и понял, что решение этой проблемы было гораздо более простым, как обычно. И поскольку мне нужно всего два растровых изображения, мне нужно гораздо меньше памяти для работы с ним.
Для рисования:
canvas.drawBitmap(mImage, mTransformationMatrix, mPaintImageRegular);
if (mFogOfWarState != FOG_OF_WAR_HIDDEN) {
canvas.drawBitmap(mFogOfWar, mTransformationMatrix, mPaintFog);
}
"Секрет" заключался в том, что вместо того, чтобы рисовать растровое изображение маски, я стираю туман войны, используя другую краску:
mFogOfWarCanvas.drawPath(mPath, mEraserPaint);
Единственный Paint
это имеет Xfermode
используется для удаления:
mEraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
И для загрузки и сохранения моей маски я делаю следующее:
private void createFogAndMask(File dataDir) {
BitmapDrawable tile = (BitmapDrawable) getResources().getDrawable(R.drawable.fog_of_war);
tile.setTileModeXY(TileMode.REPEAT, TileMode.REPEAT);
mFogOfWar = Bitmap.createBitmap(mImageBounds.width(), mImageBounds.height(), Config.ARGB_8888);
mFogOfWarCanvas = new Canvas(mFogOfWar);
tile.setBounds(mImageBounds);
tile.draw(mFogOfWarCanvas);
tile = null;
// Try to load an existing mask
File existingMask = new File(dataDir, getMaskFileName());
if (existingMask.exists()) {
Bitmap existingMaskBitmap = BitmapFactory.decodeFile(existingMask.getAbsolutePath());
mFogOfWarCanvas.drawBitmap(existingMaskBitmap, new Matrix(), mPaintImageRegular);
mFogOfWarCanvas.drawPaint(mMaskEraserPaint);
existingMaskBitmap.recycle();
System.gc();
}
}
public void saveMask(File folder) throws IOException {
if (!mReady || mImagePath == null) return;
mImage.recycle();
System.gc();
if (!folder.exists()) {
folder.mkdirs();
}
File savedFile = new File(folder, getMaskFileName());
// Change all transparent pixels to black and all non-transparent pixels to transparent
final int length = mImageBounds.width() * mImageBounds.height();
final int[] pixels = new int[length];
mFogOfWar.getPixels(pixels, 0, mImageBounds.width(), 0, 0, mImageBounds.width(), mImageBounds.height());
for (int i = 0; i < length; i++) {
if (pixels[i] == Color.TRANSPARENT) {
pixels[i] = Color.BLACK;
} else {
pixels[i] = Color.TRANSPARENT;
}
}
mFogOfWar.setPixels(pixels, 0, mImageBounds.width(), 0, 0, mImageBounds.width(), mImageBounds.height());
FileOutputStream output = new FileOutputStream(savedFile);
mFogOfWar.compress(CompressFormat.PNG, 80, output);
output.flush();
output.close();
}