圖像處理之文字軋花效果- 簡單數字水印 @ gloomyfish
首先看一下效果,左邊是一張黑白的文字圖像,右邊是混合之後的數字水印效果
實作原理
主要是利用位圖塊遷移算法,首先提取文字骨架,寬度為一個像素。然後将提取的骨架,按
照一定的像素值填充到目标圖像中即可。關于位圖塊遷移算法說明請看這裡:
http://en.wikipedia.org/wiki/Bit_blit
程式思路:
1. 首先建立兩張白闆的單色位圖,讀入黑白文字圖檔,
2. 移動一個像素位開始讀取文字圖檔中的像素,将每個對應像素與白闆單×××片疊加,直
至黑白文字圖檔完全copy到單色白闆中。
3. 重複上面操作,唯一不同的,将白闆像素移動一個像素為,以後開始填充
4. 分别将兩張位圖塊遷移圖檔與原黑白文字圖檔像素完成一個或操作,則得到左上和右下
的文字骨架。
5. 将兩個文字骨架的像素填充到目标彩×××片中,即得到軋花效果的圖檔
根據輸入參數不同,還可得到雕刻效果圖檔。
關鍵代碼解釋:
實作位圖塊遷移算法的代碼如下:
// one pixel transfer
for(int row=1; row<height; row++) {
int ta = 0, tr = 0, tg = 0, tb = 0;
for(int col=1; col<width; col++) {
index = row * width + col;
index2 = (row-1) * width + (col-1);
ta = (inPixels[isTop?index:index2] >> 24) & 0xff;
tr = (inPixels[isTop?index:index2] >> 16) & 0xff;
tg = (inPixels[isTop?index:index2] >> 8) & 0xff;
tb = inPixels[isTop?index:index2] & 0xff;
outPixels[isTop?index2:index] = (ta << 24) | (tr<< 16) | (tg << 8) | tb;
}
}
布爾變量isTop決定是否填充單色白闆位移(Offset)是零還是一。
擷取一個像素寬度骨架的方法為processonePixelWidth()主要是利用文字圖檔是一個二值圖像,
進而remove掉多餘的像素。
混合軋花的方法為embossImage()主要是簡單的像素填充,布爾變量主要是用來控制是凹軋花
還是凸軋花效果。所有對文字圖像的處理和軋花效果的處理封裝在BitBltFilter一個類中.
程式效果如下:
位圖塊位移算法實作完全源代碼如下:package com.gloomyfish.zoom.study; import java.awt.image.BufferedImage; import com.process.blur.study.AbstractBufferedImageOp; public class BitBltFilter extends AbstractBufferedImageOp { // raster operation - bit block transfer. // 1975 for the Smalltalk-72 system, For the Smalltalk-74 system private boolean isTop = true; /** * left - top skeleton or right - bottom. * * @param isTop */ public void setTop(boolean isTop) { this.isTop = isTop; } /** * blend the pixels and get the final output image * * @param textImage * @param targetImage */ public void emboss(BufferedImage textImage, BufferedImage targetImage) { // BitBltFilter filter = new BitBltFilter(); BufferedImage topImage = filter(textImage, null); setTop(false); BufferedImage buttomImage = filter(textImage, null); int width = textImage.getWidth(); int height = textImage.getHeight(); int[] inPixels = new int[width*height]; int[] outPixels = new int[width*height]; getRGB( textImage, 0, 0, width, height, inPixels ); getRGB( topImage, 0, 0, width, height, outPixels ); processonePixelWidth(width, height, inPixels, outPixels, topImage); getRGB( buttomImage, 0, 0, width, height, outPixels ); processonePixelWidth(width, height, inPixels, outPixels, buttomImage); // emboss now embossImage(topImage, targetImage, true); embossImage(buttomImage, targetImage, false); } @Override public BufferedImage filter(BufferedImage src, BufferedImage dest) { int width = src.getWidth(); int height = src.getHeight(); if ( dest == null ) dest = createCompatibleDestImage(src, null); int[] inPixels = new int[width*height]; int[] outPixels = new int[width*height]; getRGB( src, 0, 0, width, height, inPixels ); int index = 0; int index2 = 0; // initialization outPixels for(int row=0; row<height; row++) { for(int col=0; col<width; col++) { index = row * width + col; outPixels[index] = (255 << 24) | (255 << 16) | (255 << 8) | 255; } } // one pixel transfer for(int row=1; row<height; row++) { int ta = 0, tr = 0, tg = 0, tb = 0; for(int col=1; col<width; col++) { index = row * width + col; index2 = (row-1) * width + (col-1); ta = (inPixels[isTop?index:index2] >> 24) & 0xff; tr = (inPixels[isTop?index:index2] >> 16) & 0xff; tg = (inPixels[isTop?index:index2] >> 8) & 0xff; tb = inPixels[isTop?index:index2] & 0xff; outPixels[isTop?index2:index] = (ta << 24) | (tr << 16) | (tg << 8) | tb; } } setRGB( dest, 0, 0, width, height, outPixels ); return dest; } /** * * @param width * @param height * @param inPixels * @param outPixels * @param destImage */ private void processonePixelWidth(int width, int height, int[] inPixels, int[] outPixels, BufferedImage destImage) { // now get one pixel data int index = 0; for(int row=0; row<height; row++) { int ta = 0, tr = 0, tg = 0, tb = 0; int ta2 =0, tr2 = 0, tg2 = 0, tb2 = 0; for(int col=0; col<width; col++) { index = row * width + col; ta = (inPixels[index] >> 24) & 0xff; tr = (inPixels[index] >> 16) & 0xff; tg = (inPixels[index] >> 8) & 0xff; tb = inPixels[index] & 0xff; ta2 = (outPixels[index] >> 24) & 0xff; tr2 = (outPixels[index] >> 16) & 0xff; tg2 = (outPixels[index] >> 8) & 0xff; tb2 = outPixels[index] & 0xff; if(tr2 == tr && tg == tg2 && tb == tb2) { outPixels[index] = (255 << 24) | (255 << 16) | (255 << 8) | 255; } else { if(tr2 < 5 && tg2 < 5 && tb2 < 5) { outPixels[index] = (ta2 << 24) | (tr2 << 16) | (tg2 << 8) | tb2; } else { outPixels[index] = (255 << 24) | (255 << 16) | (255 << 8) | 255; } } } } setRGB( destImage, 0, 0, width, height, outPixels ); } /** * * @param src * @param dest * @param colorInverse - must be setted here!!! */ private void embossImage(BufferedImage src, BufferedImage dest, boolean colorInverse) { int width = src.getWidth(); int height = src.getHeight(); int dw = dest.getWidth(); int dh = dest.getHeight(); int[] sinPixels = new int[width*height]; int[] dinPixels = new int[dw*dh]; src.getRGB( 0, 0, width, height, sinPixels, 0, width ); dest.getRGB( 0, 0, dw, dh, dinPixels, 0, dw ); int index = 0; int index2 = 0; for ( int y = 0; y < height; y++ ) { for ( int x = 0; x < width; x++ ) { index = y * width + x; int srgb = sinPixels[index]; int r1 = (srgb >> 16) & 0xff; int g1 = (srgb >> 8) & 0xff; int b1 = srgb & 0xff; if(r1 > 200 || g1 >=200 || b1 >=200) { continue; } index2 = y * dw + x; if(colorInverse) { r1 = 255 - r1; g1 = 255 - g1; b1 = 255 - b1; } dinPixels[index2] = (255 << 24) | (r1 << 16) | (g1 << 8) | b1; } } dest.setRGB( 0, 0, dw, dh, dinPixels, 0, dw ); } }
程式測試代碼如下:
package com.gloomyfish.zoom.study; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; public class BitBltFilterTest extends JComponent { /** * */ private static final long serialVersionUID = 7462704254856439832L; private BufferedImage rawImg; private BufferedImage modImg; private Dimension mySize; public BitBltFilterTest(File f) { try { rawImg = ImageIO.read(f); modImg = ImageIO.read(new File("D:\\resource\\geanmm.png")); // modImg = ImageIO.read(new File("D:\\resource\\gloomyfish.png")); } catch (IOException e) { e.printStackTrace(); } mySize = new Dimension(2*modImg.getWidth() + 20, modImg.getHeight()+ 100); filterImage(); final JFrame imageFrame = new JFrame("Emboss Text - gloomyfish"); imageFrame.getContentPane().setLayout(new BorderLayout()); imageFrame.getContentPane().add(this, BorderLayout.CENTER); imageFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); imageFrame.pack(); imageFrame.setVisible(true); } private void filterImage() { BitBltFilter filter = new BitBltFilter(); filter.emboss(rawImg, modImg); } public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.drawImage(rawImg, 0, 0, rawImg.getWidth(), rawImg.getHeight(), null); g2.drawImage(modImg, rawImg.getWidth()+10, 0, modImg.getWidth(), modImg.getHeight(), null); g2.drawString("text image", rawImg.getWidth()/2, rawImg.getHeight()+10); g2.drawString("sharped text in image", modImg.getWidth() + 10, modImg.getHeight()+10); } public Dimension getPreferredSize() { return mySize; } public Dimension getMinimumSize() { return mySize; } public Dimension getMaximumSize() { return mySize; } public static void main(String[] args) { JFileChooser chooser = new JFileChooser(); chooser.showOpenDialog(null); File f = chooser.getSelectedFile(); new BitBltFilterTest(f); } }
轉載文章請注明出處!