java實現psd格式圖片讀入

最近在做一個java的圖片管理器的課程設計,加入了一個讀取psd格式的功能,之所以說加入,是因爲jdk本身自帶的ImageIO_API只持支持jpg、bmp、png、gif這幾種格式的圖片格式而已。

之前查過像是imagemagick之類的工具,我發現它們功能太多太強大了,而我想要做的只是讀入psd格式文件,讓其轉化成BufferImage,順帶讀取出圖片的一點信息,僅此而已,所以我認爲沒有必要動用到這種航母級的工具。

主要要做的,是找出psd格式文件裏面需要用到的字節,不需要用到的,例如圖層路徑等等的信息,則直接跳過。

下面是psd格式說明文檔(4.0.1版本)裏面的內容,可以看到它分爲5個部分:

我用到的主要是第一部分的file header和最後一部分的image data,前者保存了圖片的一些基本信息(長寬,通道數目等等),後者就是圖片像素信息。

我把讀取psd格式這一功能封裝在一個叫PsdReader的類裏面,首先是打開文件,我顯式使用了FileChannel和MappedByteBuffer加快讀取速度,起初用RandomAccessFile也是可以達到同樣目的,但是效率過低。構造方法如下:

private BufferedImage img = null;//最終獲得的目標圖片
private int[] pixels;
private RandomAccessFile raf;
private int[] byteArray;
private int[][][] channelColor;//每條通道的顏色,0號位是red,1號位是green,2號位是blue,如果是4通道,alpha直接跳過
private int[][] numOfBytePerLine;//每行的字節數,掃描圖片信息的時候是逐行進行的,因爲rle壓縮的關係,每行的字節數都不一定相同
private short numOfChannel;//通道的數目,如果是帶有透明度,則是4通道,否則通常是3通道
private int height;
private int width;
private short isRle;//壓縮方式,是0則沒有壓縮,是1則是rle壓縮
private MappedByteBuffer mbbi;
public PsdReader(File file) {
		FileChannel fc = null;
		try {
			this.raf = new RandomAccessFile(file, "r");
			fc = raf.getChannel();
			long size = fc.size();
			this.mbbi = fc.map(FileChannel.MapMode.READ_ONLY, 0, size);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.readFile();//關鍵,讀取圖片文件中的信息
		img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);  
		pixels = new int[width*height];  
		this.initPixels(pixels);
		this.setRGB(img, 0, 0, width, height, pixels);
		try {
			fc.close();
			this.raf.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

private void readFile() {
		try {
			//-------第一部分:文件頭------------------
			//通道數量
			this.mbbi.position(0x0c);//因爲一開始的4個字節,是8bps(一個符號,我理解成這個標誌代表此文件是psd格式文件,接着是兩個字節的版本信息,接着是6個字節的保留位,從0x0c開始的兩個字節,便是通道數目,之後亦是這樣讀取想要的信息,便不再累述,詳細可參考下面給出的psd格式文檔裏面的信息表
			numOfChannel = this.mbbi.getShort();
			//圖像高度
			height = this.mbbi.getInt();
			//圖像寬度
			width = this.mbbi.getInt();
			//圖像深度(每個通道的顏色位數)
			short depth = this.mbbi.getShort();
			//是rgb模式則type=3
			short type = this.mbbi.getShort();
			//--------第二部分:色彩模式信息,這部分的長度通常爲0----
			int lenOfColorModel = this.mbbi.getInt();
			this.mbbi.position(lenOfColorModel+this.mbbi.position());
			//--------第三部分:圖像資源數據------------------
			int lenOfImageResourceBlock = this.mbbi.getInt();
			//System.out.println("lenOfImageResourceBlock="+lenOfImageResourceBlock);
			this.mbbi.position(lenOfImageResourceBlock+this.mbbi.position());
			//--------第四部分:圖層與蒙版信息----------------
			int lenOfLayerInfo = this.mbbi.getInt();
			this.mbbi.position(lenOfLayerInfo+this.mbbi.position());
			//--------第五部分:圖像數據--------------------
			isRle = this.mbbi.getShort();
			
		} catch (Exception e1) {
			e1.printStackTrace();
		}


拿到位於文件最後的image data之後,便開始主要操作了:解壓。

從上圖可以看到,image data是以兩個字節的compression開始的,如果圖片未經過壓縮,則這兩個字節是00,否則是01,用editplus等工具的16進制查看器裏面可以查看出來,起初我就是這樣查找它的壓縮規律的,後來發現這樣做太愚蠢了。

因爲psd格式文檔裏面明確寫道:

由此看出,壓縮方式是固定的packBits壓縮方式,百度一下可以找到不少的關於此種壓縮的解壓和編碼方式,這裏就不累述了,下面是我用java實現的解壓方法:

private void unpackbits(int lenOfInput, int[] channelColor) {
		short n = 0;
		int last = 0;
		
		while(lenOfInput>0){
		try {
//			n = raf.readByte();
			n = this.mbbi.get();
			lenOfInput--;
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		if(0<=n && n<=127) {
			int repeatTime = n;
			++repeatTime;
			for(int t=0; t<repeatTime; t++) {
				try {
//					channelColor[last+t] = raf.readUnsignedByte();
					int ti = this.mbbi.get();
					if(ti<0) { ti += 256; }
					channelColor[last+t] = ti;
					
					lenOfInput--;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			last += repeatTime;
		}
		else if(-1>=n && n>=-127) {
			int val = 0;
			int repeatTime = -n;
			++repeatTime;
			try {
//				val = raf.readUnsignedByte();
				int ti = this.mbbi.get();
				if(ti<0) { ti += 256; }
				val = ti;
				//System.out.println(val);
				lenOfInput--;
			} catch (Exception e) {
				e.printStackTrace();
			}
			for(int t=0; t<repeatTime; t++) {
					channelColor[last+t] = val;
			}
			last += repeatTime;
		}
		else if(n==-128) {
			//noop
		}
		}
	}

如果圖片是不經過壓縮的,那就省略解壓這一步,直接將像素數據塞進數組裏面就行了。

下面是我寫的整個PsdReader的完整代碼:

package com.zer0w0.entity;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class PsdReader {

	private BufferedImage img = null;
	
	private int[] pixels;
	
	private RandomAccessFile raf;
	private int[] byteArray;
	//用來接住unsignedByte,byte不存作負數(否則拋異常,說越過顏色範圍)
	private int[][][] channelColor;
	private int[][] numOfBytePerLine;
//	private final static int RED = 0;
//	private final static int GREEN = 1;
//	private final static int BLUE = 2;
	private short numOfChannel;
	private int height;
	private int width;
	private short isRle;
	private MappedByteBuffer mbbi;
	
	public PsdReader(File file) {
		FileChannel fc = null;
		try {
			this.raf = new RandomAccessFile(file, "r");
			fc = raf.getChannel();
			long size = fc.size();
			this.mbbi = fc.map(FileChannel.MapMode.READ_ONLY, 0, size);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.readFile();
		img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);  
		pixels = new int[width*height];  
		this.initPixels(pixels);
		this.setRGB(img, 0, 0, width, height, pixels);
		try {
			fc.close();
			this.raf.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
 	
	public BufferedImage getImg() {
		return img;
	}
	
	private void initPixels(int[] pixels) {
		int index = 0;
		int a = 255;
		for(int h=0; h<this.height; h++) {
			for(int w=0; w<this.width; w++) {
				int r = this.channelColor[0][h][w];
				int g = this.channelColor[1][h][w];
				int b = this.channelColor[2][h][w];
				if(this.numOfChannel>3) {
					a = this.channelColor[3][h][w];
				}
				
				pixels[index] = (a<<24) | (r<<16)
						| (g<<8) | b;
				index++;
			}
		}
	}
	
	private void setRGB( BufferedImage image, int x, int y, int width, int height, int[] pixels ) {  
		int type = image.getType();  
		if ( type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB )  
			image.getRaster().setDataElements( x, y, width, height, pixels );  
		else  
			image.setRGB( x, y, width, height, pixels, 0, width );  
	}
	
	private void readFile() {
		try {
			//-------第一部分:文件頭------------------
			//通道數量
//			this.raf.seek(0x0c);
			this.mbbi.position(0x0c);
//			numOfChannel = this.raf.readShort();
			numOfChannel = this.mbbi.getShort();
			//System.out.println("numOfChannel="+numOfChannel);
			//圖像高度
//			height = this.raf.readInt();
			height = this.mbbi.getInt();
			//System.out.println("height="+height);
			//圖像寬度
//			width = this.raf.readInt();
			width = this.mbbi.getInt();
			//System.out.println("width="+width);
			//圖像深度(每個通道的顏色位數)
//			short depth = this.raf.readShort();
			short depth = this.mbbi.getShort();
			//System.out.println("depth="+depth);
			//是rgb模式則type=3
//			short type = this.raf.readShort();
			short type = this.mbbi.getShort();
			//System.out.println("type="+type);
			//--------第二部分:色彩模式信息,這部分的長度通常爲0----
//			int lenOfColorModel = raf.readInt();
			int lenOfColorModel = this.mbbi.getInt();
			//System.out.println("lenOfColorModel="+lenOfColorModel);
//			this.raf.seek(lenOfColorModel+this.raf.getFilePointer());//長度信息佔4個字節,但是不用加,下同
			this.mbbi.position(lenOfColorModel+this.mbbi.position());
			//--------第三部分:圖像資源數據------------------
//			int lenOfImageResourceBlock = raf.readInt();
			int lenOfImageResourceBlock = this.mbbi.getInt();
			//System.out.println("lenOfImageResourceBlock="+lenOfImageResourceBlock);
//			this.raf.seek(lenOfImageResourceBlock+this.raf.getFilePointer());
			this.mbbi.position(lenOfImageResourceBlock+this.mbbi.position());
			//--------第四部分:圖層與蒙版信息----------------
//			int lenOfLayerInfo = raf.readInt();
			int lenOfLayerInfo = this.mbbi.getInt();
			//System.out.println("lenOfLayer="+lenOfLayerInfo);
//			this.raf.seek(lenOfLayerInfo+raf.getFilePointer());
			this.mbbi.position(lenOfLayerInfo+this.mbbi.position());
			//--------第五部分:圖像數據--------------------
//			isRle = raf.readShort();
			isRle = this.mbbi.getShort();
			//System.out.println("isRle="+isRle);
//			//System.out.println("nowPosition="+this.raf.getFilePointer());
			//System.out.println("nowPosition="+this.mbbi.position());
			
		} catch (Exception e1) {
			e1.printStackTrace();
		}

		this.channelColor = new int[numOfChannel][height][width];
		if(isRle==1){
		this.numOfBytePerLine = new int[numOfChannel][height];
		for(int i=0; i<numOfChannel; i++) {
			for(int j=0; j<height; j++) {
				try {
					//TODO
//					this.numOfBytePerLine[i][j] = this.raf.readUnsignedShort();
					int ti = this.mbbi.getShort();
					if(ti<0) { ti += 65536; }
					this.numOfBytePerLine[i][j] = ti;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		for(int c=0; c<numOfChannel; c++) {
			for(int h=0; h<height; h++) {
				this.unpackbits(numOfBytePerLine[c][h],channelColor[c][h]);
			}
		}
		}else if(isRle==0) {
			for(int c=0; c<numOfChannel; c++) {
				for(int h=0; h<height; h++) {
					for(int w=0; w<width; w++) {
						try {
//							this.channelColor[c][h][w] = this.raf.readUnsignedByte();
							int ti = this.mbbi.get();
							if(ti<0) { ti += 256; }
							this.channelColor[c][h][w] = ti;
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			}
		}
	}
	
	private void unpackbits(int lenOfInput, int[] channelColor) {
		short n = 0;
		int last = 0;
		
		while(lenOfInput>0){
		try {
//			n = raf.readByte();
			n = this.mbbi.get();
			lenOfInput--;
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		if(0<=n && n<=127) {
			int repeatTime = n;
			++repeatTime;
			for(int t=0; t<repeatTime; t++) {
				try {
//					channelColor[last+t] = raf.readUnsignedByte();
					int ti = this.mbbi.get();
					if(ti<0) { ti += 256; }
					channelColor[last+t] = ti;
					
					lenOfInput--;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			last += repeatTime;
		}
		else if(-1>=n && n>=-127) {
			int val = 0;
			int repeatTime = -n;
			++repeatTime;
			try {
//				val = raf.readUnsignedByte();
				int ti = this.mbbi.get();
				if(ti<0) { ti += 256; }
				val = ti;
				//System.out.println(val);
				lenOfInput--;
			} catch (Exception e) {
				e.printStackTrace();
			}
			for(int t=0; t<repeatTime; t++) {
					channelColor[last+t] = val;
			}
			last += repeatTime;
		}
		else if(n==-128) {
			//noop
		}
		}
	}
}

使用new PsdReader(new File(psd文件的路徑)).getImg()便可以拿到這個psd文件的BufferedImage了。以上,轉載請註明出處。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章