java制作水效果_JAVA图像处理系列(十)——艺术效果:水纹

艺术效果:水纹

与火焰效果一样,借鉴粒子系统的思想,我们也可以在图像中实现类似水纹的艺术效果。

粒子系统基本上都要通过循环迭代实现,下面先看看本文方法实现的水纹效果。

第一张为原始图像,第二张为循环迭代50次后的水纹效果,第三张为通过该算法生成的动态GIF图的显示效果。

62ebc3ce3e56

cat.jpg

62ebc3ce3e56

cat-water.jpg

62ebc3ce3e56

cat-water.gif

算法实现

我们通过一个单独的类来保存粒子的状态,这里直接给出代码,感兴趣的朋友可直接通过阅读代码了解实现细节。

import java.awt.image.*;

public class WaterRoutine {

java.util.Random r = new java.util.Random(System.currentTimeMillis());

int m_iWidth;

int m_iHeight;

boolean m_bDrawWithLight;

int m_iLightModifier;

public int m_iHpage;// The current heightfield

int m_density;// The water density - can change the density...

// the height fields

int[] m_iHeightField1;

int[] m_iHeightField2;

public int getrandom(int min, int max) {

return r.nextInt(max - min) + min;

}

public int getRGB(int r, int g, int b) {

return (0x00ff & r) << 16 | (0x00ff & g) << 8 | (0x00ff & b);

}

public int getBValue(int color) {

return color & 0x00ff;

}

public int getGValue(int color) {

return (color >> 8) & 0x00ff;

}

public int getRValue(int color) {

return (color >> 16) & 0x00ff;

}

public int getRGB(BufferedImage img, int offset) {

int x = offset % img.getWidth();

int y = offset / img.getWidth();

if (y >= img.getHeight())

y = img.getHeight() - 1;

return img.getRGB(x, y);

}

public void setRGB(BufferedImage img, int offset, int rgb) {

int x = offset % img.getWidth();

int y = offset / img.getWidth();

if (y >= img.getHeight())

y = img.getHeight() - 1;

img.setRGB(x, y, rgb);

}

public WaterRoutine() {

m_iHeightField1 = null;

m_iHeightField2 = null;

m_iWidth = 0;

m_iHeight = 0;

m_bDrawWithLight = true;

m_iLightModifier = 1;

m_iHpage = 0;

m_density = 5;

}

public void create(int iWidth, int iHeight) {

// Create our height fields

m_iHeightField1 = new int[(iWidth * iHeight)];

m_iHeightField2 = new int[(iWidth * iHeight)];

// Clear our height fields

for (int i = 0; i < iWidth * iHeight; i++) {

m_iHeightField1[i] = 0;

m_iHeightField2[i] = 0;

}

m_iWidth = iWidth;

m_iHeight = iHeight;

// Set our page to 0

m_iHpage = 0;

}

public void flattenWater() {

// Clear our height fields

for (int i = 0; i < m_iWidth * m_iHeight; i++) {

m_iHeightField1[i] = 0;

m_iHeightField2[i] = 0;

}

}

public void nullPos() {

calcWater(m_iHpage, m_density);

m_iHpage ^= 1;

}

public void render(BufferedImage pSrcImage, BufferedImage pTargetImage) {

// Yes they have to be the same size...(for now)

if (m_bDrawWithLight == false) {

drawWaterNoLight(m_iHpage, pSrcImage, pTargetImage);

} else {

drawWaterWithLight(m_iHpage, m_iLightModifier, pSrcImage, pTargetImage);

}

calcWater(m_iHpage, m_density);

m_iHpage ^= 1;

}

public void calcWater(int npage, int density) {

int newh;

int count = m_iWidth + 1;

int[] newptr;

int[] oldptr;

// Set up the pointers

if (npage == 0) {

newptr = m_iHeightField1;

oldptr = m_iHeightField2;

} else {

newptr = m_iHeightField2;

oldptr = m_iHeightField1;

}

int x, y;

// Sorry, this function might not be as readable as I'd like, because

// I optimized it somewhat. (enough to make me feel satisfied with it)

for (y = (m_iHeight - 1) * m_iWidth; count < y; count += 2) {

for (x = count + m_iWidth - 2; count < x; count++) {

// This does the eight-pixel method. It looks much better.

newh = ((oldptr[count + m_iWidth] + oldptr[count - m_iWidth] + oldptr[count + 1] + oldptr[count - 1]

+ oldptr[count - m_iWidth - 1] + oldptr[count - m_iWidth + 1] + oldptr[count + m_iWidth - 1]

+ oldptr[count + m_iWidth + 1]) >> 2) - newptr[count];

newptr[count] = newh - (newh >> density);

}

}

}

public void smoothWater(int npage) {

int newh;

int count = m_iWidth + 1;

int[] newptr;

int[] oldptr;

// Set up the pointers

if (npage == 0) {

newptr = m_iHeightField1;

oldptr = m_iHeightField2;

} else {

newptr = m_iHeightField2;

oldptr = m_iHeightField1;

}

int x, y;

// Sorry, this function might not be as readable as I'd like, because

// I optimized it somewhat. (enough to make me feel satisfied with it)

for (y = 1; y < m_iHeight - 1; y++) {

for (x = 1; x < m_iWidth - 1; x++) {

// This does the eight-pixel method. It looks much better.

newh = ((oldptr[count + m_iWidth] + oldptr[count - m_iWidth] + oldptr[count + 1] + oldptr[count - 1]

+ oldptr[count - m_iWidth - 1] + oldptr[count - m_iWidth + 1] + oldptr[count + m_iWidth - 1]

+ oldptr[count + m_iWidth + 1]) >> 3) + newptr[count];

newptr[count] = newh >> 1;

count++;

}

count += 2;

}

}

public void calcWaterBigFilter(int npage, int density) {

int newh;

int count = (2 * m_iWidth) + 2;

int[] newptr;

int[] oldptr;

// Set up the pointers

if (npage == 0) {

newptr = m_iHeightField1;

oldptr = m_iHeightField2;

} else {

newptr = m_iHeightField2;

oldptr = m_iHeightField1;

}

int x, y;

// Sorry, this function might not be as readable as I'd like, because

// I optimized it somewhat. (enough to make me feel satisfied with it)

for (y = 2; y < m_iHeight - 2; y++) {

for (x = 2; x < m_iWidth - 2; x++) {

// This does the 25-pixel method. It looks much okay.

newh = ((((oldptr[count + m_iWidth] + oldptr[count - m_iWidth] + oldptr[count + 1]

+ oldptr[count - 1]) << 1)

+ ((oldptr[count - m_iWidth - 1] + oldptr[count - m_iWidth + 1] + oldptr[count + m_iWidth - 1]

+ oldptr[count + m_iWidth + 1]))

+ ((oldptr[count - (m_iWidth * 2)] + oldptr[count + (m_iWidth * 2)] + oldptr[count - 2]

+ oldptr[count + 2]) >> 1)

+ ((oldptr[count - (m_iWidth * 2) - 1] + oldptr[count - (m_iWidth * 2) + 1]

+ oldptr[count + (m_iWidth * 2) - 1] + oldptr[count + (m_iWidth * 2) + 1]

+ oldptr[count - 2 - m_iWidth] + oldptr[count - 2 + m_iWidth]

+ oldptr[count + 2 - m_iWidth] + oldptr[count + 2 + m_iWidth]) >> 2)) >> 3)

- (newptr[count]);

newptr[count] = newh - (newh >> density);

count++;

}

count += 4;

}

}

public void HeightBlob(int x, int y, int radius, int height, int page) {

int rquad;

int cx, cy, cyq;

int left, top, right, bottom;

int[] newptr;

int[] oldptr;

// Set up the pointers

if (page == 0) {

newptr = m_iHeightField1;

oldptr = m_iHeightField2;

} else {

newptr = m_iHeightField2;

oldptr = m_iHeightField1;

}

rquad = radius * radius;

// Make a randomly-placed blob...

if (x < 0)

x = 1 + radius + r.nextInt(65536) % (m_iWidth - 2 * radius - 1);

if (y < 0)

y = 1 + radius + r.nextInt(65536) % (m_iHeight - 2 * radius - 1);

left = -radius;

right = radius;

top = -radius;

bottom = radius;

// Perform edge clipping...

if (x - radius < 1)

left -= (x - radius - 1);

if (y - radius < 1)

top -= (y - radius - 1);

if (x + radius > m_iWidth - 1)

right -= (x + radius - m_iWidth + 1);

if (y + radius > m_iHeight - 1)

bottom -= (y + radius - m_iHeight + 1);

for (cy = top; cy < bottom; cy++) {

cyq = cy * cy;

for (cx = left; cx < right; cx++) {

if (cx * cx + cyq < rquad)

newptr[m_iWidth * (cy + y) + (cx + x)] += height;

}

}

}

public void HeightBox(int x, int y, int radius, int height, int page) {

int cx, cy;

int left, top, right, bottom;

int[] newptr;

int[] oldptr;

// Set up the pointers

if (page == 0) {

newptr = m_iHeightField1;

oldptr = m_iHeightField2;

} else {

newptr = m_iHeightField2;

oldptr = m_iHeightField1;

}

if (x < 0)

x = 1 + radius + r.nextInt(65536) % (m_iWidth - 2 * radius - 1);

if (y < 0)

y = 1 + radius + r.nextInt(65536) % (m_iHeight - 2 * radius - 1);

left = -radius;

right = radius;

top = -radius;

bottom = radius;

// Perform edge clipping...

if (x - radius < 1)

left -= (x - radius - 1);

if (y - radius < 1)

top -= (y - radius - 1);

if (x + radius > m_iWidth - 1)

right -= (x + radius - m_iWidth + 1);

if (y + radius > m_iHeight - 1)

bottom -= (y + radius - m_iHeight + 1);

for (cy = top; cy < bottom; cy++) {

for (cx = left; cx < right; cx++) {

newptr[m_iWidth * (cy + y) + (cx + x)] = height;

}

}

}

public void WarpBlob(int x, int y, int radius, int height, int page) {

int cx, cy;

int left, top, right, bottom;

int square;

int radsquare = radius * radius;

int[] newptr;

int[] oldptr;

// Set up the pointers

if (page == 0) {

newptr = m_iHeightField1;

oldptr = m_iHeightField2;

} else {

newptr = m_iHeightField2;

oldptr = m_iHeightField1;

}

// radsquare = (radius*radius) << 8;

radsquare = (radius * radius);

height /= 64;

left = -radius;

right = radius;

top = -radius;

bottom = radius;

// Perform edge clipping...

if (x - radius < 1)

left -= (x - radius - 1);

if (y - radius < 1)

top -= (y - radius - 1);

if (x + radius > m_iWidth - 1)

right -= (x + radius - m_iWidth + 1);

if (y + radius > m_iHeight - 1)

bottom -= (y + radius - m_iHeight + 1);

for (cy = top; cy < bottom; cy++) {

for (cx = left; cx < right; cx++) {

square = cy * cy + cx * cx;

// square <<= 8;

if (square < radsquare) {

// Height[page][WATERWID*(cy+y) + cx+x]

// += (sqrt(radsquare)-sqrt(square))*height;

newptr[m_iWidth * (cy + y) + cx + x] += (int) ((radius - Math.sqrt(square)) * (float) (height));

}

}

}

}

public void sineBlob(int x, int y, int radius, int height, int page) {

int cx, cy;

int left, top, right, bottom;

int square, dist;

int radsquare = radius * radius;

float length = (float) ((1024.0 / (float) radius) * (1024.0 / (float) radius));

int[] newptr;

int[] oldptr;

// Set up the pointers

if (page == 0) {

newptr = m_iHeightField1;

oldptr = m_iHeightField2;

} else {

newptr = m_iHeightField2;

oldptr = m_iHeightField1;

}

if (x < 0)

x = 1 + radius + r.nextInt(65536) % (m_iWidth - 2 * radius - 1);

if (y < 0)

y = 1 + radius + r.nextInt(65536) % (m_iHeight - 2 * radius - 1);

// radsquare = (radius*radius) << 8;

radsquare = (radius * radius);

// height /= 8;

left = -radius;

right = radius;

top = -radius;

bottom = radius;

// Perform edge clipping...

if (x - radius < 1)

left -= (x - radius - 1);

if (y - radius < 1)

top -= (y - radius - 1);

if (x + radius > m_iWidth - 1)

right -= (x + radius - m_iWidth + 1);

if (y + radius > m_iHeight - 1)

bottom -= (y + radius - m_iHeight + 1);

for (cy = top; cy < bottom; cy++) {

for (cx = left; cx < right; cx++) {

square = cy * cy + cx * cx;

if (square < radsquare) {

dist = (int) (Math.sqrt(square * length));

newptr[m_iWidth * (cy + y) + cx + x] += (int) ((Math.cos(dist) + 0xffff) * (height)) >> 19;

}

}

}

}

public void drawWaterNoLight(int page, BufferedImage pSrcImage, BufferedImage pTargetImage) {

int dx, dy;

int x, y;

int c;

int offset = m_iWidth + 1;

int[] ptr = m_iHeightField1;

for (y = (m_iHeight - 1) * m_iWidth; offset < y; offset += 2) {

for (x = offset + m_iWidth - 2; offset < x; offset++) {

if (offset + m_iWidth >= m_iWidth * m_iHeight)

continue;

dx = ptr[offset] - ptr[offset + 1];

dy = ptr[offset] - ptr[offset + m_iWidth];

// c = pSrcImage[offset + m_iWidth*(dy>>3) + (dx>>3)];

c = getRGB(pSrcImage, offset + m_iWidth * (dy >> 3) + (dx >> 3));

// pTargetImage[offset] = c;

setRGB(pTargetImage, offset, c);

offset++;

if (offset + m_iWidth >= m_iWidth * m_iHeight)

continue;

dx = ptr[offset] - ptr[offset + 1];

dy = ptr[offset] - ptr[offset + m_iWidth];

// c = pSrcImage[offset + m_iWidth*(dy>>3) + (dx>>3)];

// pTargetImage[offset] = c;

c = getRGB(pSrcImage, offset + m_iWidth * (dy >> 3) + (dx >> 3));

setRGB(pTargetImage, offset, c);

}

}

}

public void drawWaterWithLight(int page, int LightModifier, BufferedImage pSrcImage, BufferedImage pTargetImage) {

int dx, dy;

int x, y;

int c;

int offset = m_iWidth + 1;

int lIndex;

int lBreak = m_iWidth * m_iHeight;

int[] ptr = m_iHeightField1;

for (y = (m_iHeight - 1) * m_iWidth; offset < y; offset += 2) {

for (x = offset + m_iWidth - 2; offset < x; offset++) {

if (offset + m_iWidth >= m_iWidth * m_iHeight)

continue;

dx = ptr[offset] - ptr[offset + 1];

dy = ptr[offset] - ptr[offset + m_iWidth];

lIndex = offset + m_iWidth * (dy >> 3) + (dx >> 3);

if (lIndex < lBreak && lIndex > 0) {

// c = pSrcImage[lIndex];// - (dx>>LightModifier);

c = getRGB(pSrcImage, lIndex);

// Now we shift it by the dx component...

//

c = getShiftedColor(c, dx);

// pTargetImage[offset] = c;

setRGB(pTargetImage, offset, c);

}

offset++;

if (offset + m_iWidth >= m_iWidth * m_iHeight)

continue;

dx = ptr[offset] - ptr[offset + 1];

dy = ptr[offset] - ptr[offset + m_iWidth];

lIndex = offset + m_iWidth * (dy >> 3) + (dx >> 3);

if (lIndex < lBreak && lIndex > 0) {

// c = pSrcImage[lIndex];// - (dx>>LightModifier);

c = getRGB(pSrcImage, lIndex);

c = getShiftedColor(c, dx);

// temp[offset] = (c < 0) ? 0 : (c > 255) ? 255 : c;

// pTargetImage[offset] = c;

setRGB(pTargetImage, offset, c);

}

}

}

}

public int getShiftedColor(int color, int shift) {

int R;

int G;

int B;

int ir;

int ig;

int ib;

R = getRValue(color) - shift;

G = getGValue(color) - shift;

B = getBValue(color) - shift;

ir = (R < 0) ? 0 : (R > 255) ? 255 : R;

ig = (G < 0) ? 0 : (G > 255) ? 255 : G;

ib = (B < 0) ? 0 : (B > 255) ? 255 : B;

return getRGB(ir, ig, ib);

}

}

测试代码

下面是测试代码:

public class GifEffectWaterTest {

public static void waterImage(int xpos, int ypos) throws IOException {

BufferedImage img = ImageIO.read(new File("D:\\eclipse-workspace\\image-toolkit\\images\\cat.jpg"));

WaterRoutine wr = new WaterRoutine();

wr.create(img.getWidth(), img.getHeight());

wr.HeightBlob(xpos, ypos, 50, 500, wr.m_iHpage);

for (int i = 0; i < 50; i++) {

wr.nullPos();

}

BufferedImage resultImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);

wr.render(img, resultImage);

ImageIO.write(resultImage, "jpeg", new File("D:\\eclipse-workspace\\image-toolkit\\images\\cat-water.jpg"));

}

public static Vector waterEffect(BufferedImage img, int xpos, int ypos) {

Vector images = new Vector<>();

WaterRoutine wr = new WaterRoutine();

wr.create(img.getWidth(), img.getHeight());

wr.HeightBlob(xpos, ypos, 50, 500, wr.m_iHpage);

for (int i = 0; i < 200; i++) {

if (i % 10 == 0) {

BufferedImage tmpImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);

wr.render(img, tmpImg);

images.add(tmpImg);

} else {

wr.nullPos();

}

}

return images;

}

public static void waterImageGif(int xpos, int ypos) throws IOException {

BufferedImage img = ImageIO.read(new File("D:\\eclipse-workspace\\image-toolkit\\images\\cat.jpg"));

OutputStream out = new FileOutputStream("D:\\eclipse-workspace\\image-toolkit\\images\\cat-water.gif");

GifEncoder gif = new GifEncoder();

if (!gif.isInit()) {

gif.init(img.getWidth(), img.getHeight(), out, true);

gif.setDelay(10);

}

Vector images = waterEffect(img, xpos, ypos);

for (BufferedImage item : images) {

gif.addImage(item);

}

gif.setCount(0);

gif.encodeMultiple();

out.close();

}

public static void main(String[] argv) throws IOException {

waterImage(100, 300);

waterImageGif(100, 300);

}

}

其中waterEffect实现迭代50次后的水纹效果,waterImageGif实现每迭代10次生成一张图片,叠加或生成动态GIF图片。GIF图片生成类GifEncoder请参考文章《JAVA实现GIF动画》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值