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了。以上,转载请注明出处。



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