@
背景
用Java實現字符拼成一個圖片,先看一下效果:
- 左邊是原圖,右邊是用字符拼成的效果,即寫好代碼,讀入一張圖片,然后用指定的字符把這個圖片的輪廓拼出來
放大之后是這個樣子 - 用love拼成了哆啦A夢的輪廓
源碼
public class AscPic {
public static void main(String[] args) throws IOException {
String path = "d:/qqq.png";//導入的圖片
String base = "love";//將會用這個字符串里的字符填充圖片
BufferedImage image = ImageIO.read(new File(path));//讀入圖片,並用圖片緩沖區對象來接收
//雙層for循環,遍歷圖片
for (int y = 0; y < image.getHeight(); y++) {//先豎向遍歷,再橫向遍歷,即一行一行的找,后面也會一行一行的打印
for (int x = 0; x < image.getWidth(); x++) {
int color = image.getRGB(x, y);//圖片緩沖區自帶的方法,可以得到當前點的顏色值,返回值是int類型
int r=(color>>16)&0xff;
int g=(color>>8)&0xff;
int b=color&0xff;
float gray = 0.299f * r + 0.578f * g + 0.114f * b;//灰度值計算公式,固定比例,無需理解
int index = Math.round(gray * (base.length()) / 255);
if(index>=base.length()) {
System.out.print(" ");//白色的地方打空格,相當於白色背景,這樣圖片輪廓比較明顯
}else {
System.out.print(base.charAt(index));//有顏色的地方打字符
}
}
System.out.println();//一行打完,換行
}
}
}
代碼思路
整體思路為導入想好處理的圖片,遍歷,得到每個像素點的顏色,然后將其轉換成灰度值(也就是把彩色轉換成黑白),根據得到的灰度值計算字符串索引,達到效果就是不同顏色可以對應不同的下標,以此來匹配字符串中的字符,最后按照原有的坐標點把這些字符打印出來即可
- 定義好想要填充的字符串
- 導入想要處理的圖片,需要用到BufferedImage(圖片緩沖區)這個對象
- 遍歷整張圖片,這里需要注意,外層循環遍歷y軸,內層遍歷x軸,因為打印的時候需要一行一行打印,打完一行要換行
- 根據getRGB(x,y)方法,傳入當前的坐標點,得到當前點的顏色
- 從得到的顏色中單獨拆分出r,g,b的值
- 根據得到的rgb計算對應的灰度值
- 根據灰度值計算索引
- 打印
難點講解
如何得到rgb
int color = image.getRGB(x, y);//圖片緩沖區自帶的方法,可以得到當前點的顏色值,返回值是int類型
int r=(color>>16)&0xff;
int g=(color>>8)&0xff;
int b=color&0xff;
首先明確一點,所謂的rgb就是三原色,red,green,blue,無論是ps還是程序,都會通過給rgb賦值來拼成一個新的顏色。通過etRGB(x,y)得到的顏色是一個int類型,我們用color來表示。這個值包含四部分內容,分別為a,r,g,b,即透明度,red,green,blue。每一部分恰好占一個字節。所以我們要做的就是從這個int中去單獨得到從左數第二,第三,第四字節的數值。
怎么做呢?先來回顧兩個位運算的基本知識:
- "&",與,都是0則結果為0,都是1結果為1,一個1一個0結果為0.從數學上理解,&操作符其實是在取交集。
7&5=?
首先換算成二進制,7的二進制是0111,5的二進制是0101
0111&0101=0101,還是5。
通過上面的計算有沒有發現一個規律,如果我想要讓一個數和另一個數&完結果還是這個數本身怎么辦?比如我想讓0101,0111,0011和一個數&完結果還是他們本身,那么這個數應該是多少。 - ">>",右移.
8>>2,表示8向右平移兩位,結果為2.
8的二進制1000,右移兩位,0010.
現在想想,怎么樣通過上面兩個符號從一個int中得到某一個字節的數值,比如得到第二字節的值,也就是r的值。
假裝你們想了20分鍾。
我們用二進制來看,假設我們得到的color換算成二進制是:
01111100 01011010 10001101 00111110
我們要得到從左數第二個字節的值,怎么辦?
首先,把這個數向右平移兩個字節,也就是16位,那么就是color>>16,結果為
00000000 00000000 01111100 01011010
此時,再和11111111與一下,是不是就得到這個數本身了。
00000000 00000000 01111100 01011010
&
00000000 00000000 00000000 11111111
=
00000000 00000000 00000000 01011010
現在得到的就是r的值。寫法:(color>>16)& 0xff.
0x表示16進制,16進制的ff表示的就是二進制的8個1。
以此類推,得到g就是(color>>8)& 0xff,得到b就是color & 0xff。
注:這里注意,方法不唯一,也可以先與后右移。這種方法是用位運算符來做的,當然也能轉換成二進制數組然后拆分。那么為什么要用位運算符來做呢,第一是方便,第二就是很有逼格啊老鐵。
如何讓不同顏色匹配不同字符
int index = Math.round(gray * (base.length()) / 255);
gray是我們求出的灰度值,它的最大值也是255。不同顏色得到的gray不一樣,同理,gray/255得到的就是不同顏色對應的一個比例,用這個比例乘以字符串長度就完成了不同顏色或者說不同顏色段匹配了不同的字符下標。因此得到gray * (base.length()) / 255,然后通過Math.round()方法四舍五入取整。
這里需要注意一下, 這樣打印出來的字符圖片色調會比較深,為了讓顏色區分更明顯,我們可以讓一些接近於白色的淺色都打印成空格,那么我們就寫成
int index = Math.round(gray * (base.length()+1) / 255);
把字符串長度加一,然后再做乘除操作,這樣會讓一部分顏色對應的索引大於字符串長度。
我們打印的時候是這樣判斷的,如果index大於等於字符串長度就打印空格,如果不是才打印字符。
為什么我的圖片只能打一半
eclipse的控制台輸出的行數有限,超過規定行數就覆蓋了。這時可以把y++,改成y+=2,相當於每隔兩個點取一下顏色並打印,也就是把原圖縱向上縮小了一半。為了保持原圖的比例,同理也可以把x做相應調整。
OutOfMemoryError錯誤
內存溢出,圖片太大了,換個小一點的圖片
為什么用漢字來打印圖案會扭曲
字母或字符占得大小比漢字要小,漢字所占的空間很大,如果按照原有的點來打印漢字,因為漢字的寬度幾乎比字母大一倍,所以會被擠出去,造成了扭曲。解決方法:改變y和x的自增數,也就是把x++改成+2,+3這樣的,橫向上每隔兩個點打印一下,給漢字預留出空間
裝逼時刻
找你女神的照片來打印,字符可以用“love”或者“我愛你”
放大
互聯網+時代,表白當然也要用技術手段表白,程序員不是不解風情,我們的浪漫,與眾不同。