Как создать маску для прозрачного наложения? - PullRequest
5 голосов
/ 16 февраля 2012

У меня есть следующий сценарий: растровое изображение, которое используется в качестве фона, и другое растровое изображение, которое используется в качестве наложения, которое может быть прозрачным на 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));

Это то, что я получаю с текущейКонфигурация должна быть более понятной: screenshot

Я не уверен, смогу ли я выполнить эту настройку альфа на объекте 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 Ответ

5 голосов
/ 23 февраля 2012

Я попробовал совершенно другой подход после какого-то прозрения - и понял, что решение этой проблемы было гораздо более простым подходом - как обычно. А поскольку мне нужно всего два растровых изображения, мне нужно гораздо меньше памяти для работы с ним.

Для рисования:

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();     
}
...