Help Drawing Sprites Using Masks [SOLVED]

Lately I’ve been trying to figure out how to draw sprites with partially transparent areas and found that using sprites with masks is the way to do it. After reading through the documentation in the Arduboy2 library for drawExternalMask() I gave it a go myself using this image:

car-export car_mask-export

I then drew the second image to use as a mask for the sprite as I’m lead to believe that the white areas of the mask are the areas of the sprite that will be drawn, leaving the black areas in the mask transparent in the drawn sprite.

My expected output when using drawExternalMask() with the sprite and mask was for it to look something like this against a white background:

car_on_background-export

and like this when intersecting a black rectangle:

car_on_background_02-export

What I actually get is an image that looks like the mask has been shifted to the right a few pixels or something, producing an image like this where parts of the back of the car seem to be transparent and drawing the white square behind it.

car_on_background_03-export
(Recreation of the image seen on screen)

My Code

Main Sketch File:

#include <Arduboy2.h>
Arduboy2 arduboy;

#include "sprites.h"

int x = 64;
int y = 32;

void setup() {
//  arduboy.begin();
  arduboy.boot();
  arduboy.audio.begin();
  arduboy.setFrameRate(60);  
}

void loop() {
  if (!(arduboy.nextFrame())) return;
//  arduboy.clear();
  arduboy.fillRect(0, 0, 128, 64, BLACK);

  if(arduboy.pressed(LEFT_BUTTON))  {x -= 1;}
  if(arduboy.pressed(RIGHT_BUTTON)) {x += 1;}
  if(arduboy.pressed(UP_BUTTON))    {y -= 1;}
  if(arduboy.pressed(DOWN_BUTTON))  {y += 1;}

  arduboy.fillRect(88, 8, 32, 32, WHITE);

  Sprites::drawOverwrite(24, 24, CarSprite, 0);
  Sprites::drawExternalMask(x, y, CarSprite, CarMask, 0, 0);
  
  arduboy.display();
}

Sprites File:

#pragma once

const uint8_t CarSprite[] PROGMEM = {
  24, 16,

  // Frame 0 - Static Right
  0xe0, 0x10, 0xc8, 0xe8, 0xe8, 0x4, 0x72, 0xfa, 0xfa, 0xfa, 0xfa, 0x7a, 0x2, 0x72, 0xe4, 0x8, 0xd0, 0xd0, 0xd0, 0xd0, 0x90, 0x20, 0xc0, 0x00, 0x7, 0x18, 0x23, 0x47, 0x43, 0x5b, 0x5b, 0x47, 0x47, 0x47, 0x46, 0x46, 0x46, 0x42, 0x5a, 0x5a, 0x47, 0x4f, 0x4f, 0x2b, 0x23, 0x10, 0xf, 0x00,
};

const uint8_t CarMask[] PROGMEM = {
  24, 16,

  // Frame 0 - Static Right Mask
  0xe0, 0xf0, 0xf8, 0xf8, 0xf8, 0xfc, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfc, 0xf8, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xe0, 0xc0, 0x00, 0x7, 0x1f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x3f, 0x3f, 0x1f, 0xf, 0x00,
};

If anyone can see where I’m going wrong help would be greatly appreciated as always.

1 Like

Your mask has the two width/height bytes in the beginning of the mask. Just delete them.

const uint8_t CarMask[] PROGMEM = {
  // Frame 0 - Static Right Mask
  0xe0, 0xf0, 0xf8, 0xf8, 0xf8, 0xfc, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfc, 0xf8, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xe0, 0xc0, 0x00, 0x7, 0x1f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x3f, 0x3f, 0x1f, 0xf, 0x00,
};

Oh I see, how come the mask doesn’t need the width and height then? Does it implicitly use the sprites width and height?

Thanks for your help :slight_smile:

Aaarghh that was all I need to do! It works fine now, can’t believe I didn’t try that…

Yup. :slight_smile:

I don’t know what you’re using for the conversion, but if you drag-and-drop a PNG with transparency into ProjectABE’s editor you’ll get your arrays in a few different formats to pick from. You might also want to look into the compressed bitmaps, they may save you some space.

Oooooo fancy! I haven’t had a chance to try out your emulator yet but I can see how useful it would be for development and testing.

I’ve been using ToChars by Crait for all my sprites so far, it’s simple and gets the job done so that’s good enough for me :man_shrugging:

1 Like

This is answered in the Arduboy2 library documentation for the Sprites (and SpritesB) class.

For a separate mask array, as is used with drawExternalMask(), the width and height are not included but must contain data of the same dimensions as the corresponding image array.

His question was why? That quote really doesn’t explain why. I always thought that they should be included so you can use the mask as a bitmap in its own right if need be.

Just make the mask a bitmap and index in 2 bytes to use it as a mask.

Right … hadn’t considered that but true be told I haven’t actually needed to use the mask by itself. Nice little solution if needed …