2011-12-30 3 views
6

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

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

Есть ли способ предоставить 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?

ответ

6

Вы можете использовать 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

+0

Эй, Лоурен! Спасибо, что ответили. Большое вам спасибо за то, что вы показали мне, как это сделать! –

+0

почему t.scale (1, 1); ?? AFAIK, что инструкция ничего не делает? (масштаб до его фактического размера?) – pakore

0

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

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

Для примера: 100x100 изображение, и это 50х50 изменен/передискретизируется аналог:

resampling

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

+0

Это также реализует двойной буфер, поэтому вы можете делать несколько изменений в изображении в памяти, не обновляя пользовательский интерфейс, который может быть интенсивным процессором. – lawrencealan

+0

Изображение шарика, перемещающегося по экрану, но его движение очень медленно. Его движущийся 1 пиксель каждую секунду. Если я рисую 2 раза на экран каждую секунду, то во второй раз рисунок будет точно таким же, как в первый раз. В третий раз покажет шар, который за один шаг переместил бы один пиксель. Вместо этого я хочу, чтобы мяч был перемещен частично, чтобы его можно было нарисовать, как если бы он находился между двумя пикселями. Доступна ли эта функция в java? –

+0

Вы должны подражать этому, используя описанный выше процесс. Скажите, что ваша область рендеринга - 800x600, вам нужно изображение в памяти, равное 2x, - 1600x1200, - и вместо того, чтобы двигаться на уровне с плавающей точкой, вы перемещаетесь по целым пикселям ... поэтому вместо перемещения шара 0,5 px вы перемещаете его на 1 пиксель в увеличенном изображении на основе памяти, а когда вы перепробируете его в область видимого экрана (800x600), это будет эмулировать точность подпикселя. – lawrencealan

3

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

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

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; 
} 

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

+0

Как я буду делать такие операции в java? Могут ли быть сделаны оптимизации? У меня много объектов, и вычисление чего-то вроде этого похоже на то, что это может занять некоторое время ... –

+0

Я не очень свободно владею Java, но, по-видимому, у ваших спрайтов есть некоторая структура, не так ли? На языках C, как правило, есть указатель на первый пиксель и количество байтов на строку. Я предполагаю, что в Java у вас, вероятно, есть массив пикселей без пробелов между строками. Таким образом, спрайт может буквально быть просто большим массивом красного, зеленого, синего, альфа (или, возможно, альфа, красного, зеленого, синего). Поэтому вместо вычисления адреса структуры пикселя вы просто получите необходимые вам смещения. В bilinearInterp первые 2 строки просто вычисляют индекс 4 пикселей вокруг нужной точки. – user1118321

+0

Это правильно, но вычислять для каждого кадра в моем коде может быть не самый лучший способ. Я надеялся, что может быть какая-то встроенная функция как часть Java API, которая это выполняет. –

1

Я успешно решить мою проблему после того, как делать что-то вроде 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

Спасибо за сообщение; В итоге я тоже сделал что-то похожее; он определенно несчастлив, что drawImage не поддерживает этого. –

+0

Удивительно большое спасибо! Кроме того, я использовал 'g.setRenderingHint (RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);' чтобы он выглядел еще лучше. – Ajay

 Смежные вопросы

  • Нет связанных вопросов^_^