Рисование изображения с использованием субпиксельной точности с использованием Graphics2D - PullRequest
7 голосов
/ 30 декабря 2011

Я сейчас пытаюсь рисовать изображения на экране с обычной скоростью, как в видеоигре.

К сожалению, из-за скорости, с которой изображение движется, некоторые кадры идентичны, потому что изображение еще не переместилось на полный пиксель.

Есть ли способ предоставить float значения для Graphics2D для экранной позиции для рисования изображения, а не int значения?

Изначально вот что я сделал:

BufferedImage srcImage = sprite.getImage ( );
Position imagePosition = ... ; //Defined elsewhere
g.drawImage ( srcImage, (int) imagePosition.getX(), (int) imagePosition.getY() );

Это, конечно, пороговые значения, поэтому изображение не перемещается между пикселями, а переходит от одного к следующему.

Следующим методом было установить цвет краски вместо текстуры и рисовать в указанной позиции. К сожалению, это дало неверные результаты, которые показывали мозаику, а не корректное сглаживание.

g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );

BufferedImage srcImage = sprite.getImage ( );

g.setPaint ( new TexturePaint ( srcImage, new Rectangle2D.Float ( 0, 0, srcImage.getWidth ( ), srcImage.getHeight ( ) ) ) );

AffineTransform xform = new AffineTransform ( );

xform.setToIdentity ( );
xform.translate ( onScreenPos.getX ( ), onScreenPos.getY ( ) );
g.transform ( xform );

g.fillRect(0, 0, srcImage.getWidth(), srcImage.getHeight());

Что я должен сделать, чтобы добиться желаемого эффекта субпиксельной визуализации изображения в Java?

Ответы [ 4 ]

8 голосов
/ 05 января 2012

Вы можете использовать BufferedImage и AffineTransform, нарисовать буферизованное изображение, а затем нарисовать буферизованное изображение для компонента в событии рисования.

    /* overrides the paint method */
    @Override
    public void paint(Graphics g) {
        /* clear scene buffer */
        g2d.clearRect(0, 0, (int)width, (int)height);

        /* draw ball image to the memory image with transformed x/y double values */
        AffineTransform t = new AffineTransform();
        t.translate(ball.x, ball.y); // x/y set here, ball.x/y = double, ie: 10.33
        t.scale(1, 1); // scale = 1 
        g2d.drawImage(image, t, null);

        // draw the scene (double percision image) to the ui component
        g.drawImage(scene, 0, 0, this);
    }

Проверьте мой полный пример здесь: http://pastebin.com/hSAkYWqM

3 голосов
/ 30 декабря 2011

Вы можете составить изображение самостоятельно, используя субпиксельную точность, но это больше работы с вашей стороны.Простая билинейная интерполяция должна работать достаточно хорошо для игры.Ниже приведен код psuedo-C ++ для этого.

Обычно, чтобы нарисовать спрайт в местоположении (a, b), вы должны сделать что-то вроде этого:

for (x = a; x < a + sprite.width; x++)
{
    for (y = b; y < b + sprite.height; y++)
    {
        *dstPixel = alphaBlend (*dstPixel, *spritePixel);
        dstPixel++;
        spritePixel++;
    }
    dstPixel += destLineDiff; // Move to start of next destination line
    spritePixel += spriteLineDiff; // Move to start of next sprite line
}

Чтобы выполнить подпрограмму-пиксельное рендеринг, вы делаете тот же цикл, но учитываете смещение субпикселя следующим образом:

float xOffset = a - floor (a);
float yOffset = b - floor (b);
for (x = floor(a), spriteX = 0; x < floor(a) + sprite.width + 1; x++, spriteX++)
{
    for (y = floor(b), spriteY = 0; y < floor (b) + sprite.height + 1; y++, spriteY++)
    {
        spriteInterp = bilinearInterp (sprite, spriteX + xOffset, spriteY + yOffset);
        *dstPixel = alphaBlend (*dstPixel, spriteInterp);
        dstPixel++;
        spritePixel++;
    }
    dstPixel += destLineDiff; // Move to start of next destination line
    spritePixel += spriteLineDiff; // Move to start of next sprite line
}

Функция bilinearInterp () будет выглядеть примерно так:

Pixel bilinearInterp (Sprite* sprite, float x, float y)
{
    // Interpolate the upper row of pixels
    Pixel* topPtr = sprite->dataPtr + ((floor (y) + 1) * sprite->rowBytes) + floor(x) * sizeof (Pixel);
    Pixel* bottomPtr = sprite->dataPtr + (floor (y) * sprite->rowBytes) + floor (x) * sizeof (Pixel);

    float xOffset = x - floor (x);
    float yOffset = y - floor (y);

    Pixel top = *topPtr + ((*(topPtr + 1) - *topPtr) * xOffset;
    Pixel bottom = *bottomPtr + ((*(bottomPtr + 1) - *bottomPtr) * xOffset;
    return bottom + (top - bottom) * yOffset;
}

Это не должно использовать дополнительную память, но для рендеринга потребуется дополнительное время.

1 голос
/ 27 ноября 2014

Я успешно решил свою проблему после того, как сделал что-то, похожее на предложенный lawrencealan.

Изначально у меня был следующий код, где g преобразуется в систему координат 16: 9 до вызова метода:

private void drawStar(Graphics2D g, Star s) {

    double radius = s.getRadius();
    double x = s.getX() - radius;
    double y = s.getY() - radius;
    double width = radius*2;
    double height = radius*2;

    try {

        BufferedImage image = ImageIO.read(this.getClass().getResource("/images/star.png"));
        g.drawImage(image, (int)x, (int)y, (int)width, (int)height, this);

    } catch (IOException ex) {
        Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex);
    }

}

Однако, как заметил спрашивающий Каушик Шанкар, превращение двойных позиций в целые числа заставляет изображение «прыгать» вокруг, а превращение двойных измерений в целые числа делает его масштабируемым «скачком» (почему, черт возьми, это делает g.drawImage не принимает двойников ?!).То, что я нашел для меня работой, было следующим:

private void drawStar(Graphics2D g, Star s) {

    AffineTransform originalTransform = g.getTransform();

    double radius = s.getRadius();
    double x = s.getX() - radius;
    double y = s.getY() - radius;
    double width = radius*2;
    double height = radius*2;

    try {

        BufferedImage image = ImageIO.read(this.getClass().getResource("/images/star.png"));

        g.translate(x, y);
        g.scale(width/image.getWidth(), height/image.getHeight());

        g.drawImage(image, 0, 0, this);

    } catch (IOException ex) {
        Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex);
    }

    g.setTransform(originalTransform);

}

Похоже, это глупый способ сделать это.

0 голосов
/ 30 декабря 2011

Измените разрешение вашего изображения соответствующим образом, нет такого понятия, как растровое изображение с субпиксельными координатами, поэтому в основном вы можете создать изображение в памяти, большее, чем то, что вы хотите отобразить на экране, но позволяет вам "субпиксель "точность.

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

Например: изображение 100x100 иэто 50x50 измененный / измененный размер аналога:

resampling

См .: http://en.wikipedia.org/wiki/Resampling_%28bitmap%29

...