001package net.gdface.image;
002
003import java.awt.Color;
004import java.awt.Graphics;
005import java.awt.Graphics2D;
006import java.awt.Image;
007import java.awt.Rectangle;
008import java.awt.Transparency;
009import java.awt.color.ColorSpace;
010import java.awt.image.BufferedImage;
011import java.awt.image.ColorConvertOp;
012import java.awt.image.ColorModel;
013import java.awt.image.ComponentColorModel;
014import java.awt.image.ComponentSampleModel;
015import java.awt.image.DataBuffer;
016import java.awt.image.DataBufferByte;
017import java.awt.image.Raster;
018import java.awt.image.RenderedImage;
019import java.awt.image.WritableRaster;
020import java.io.ByteArrayInputStream;
021import java.io.ByteArrayOutputStream;
022import java.io.IOException;
023import java.io.OutputStream;
024import java.util.Arrays;
025import java.util.Iterator;
026
027import javax.imageio.IIOException;
028import javax.imageio.IIOImage;
029import javax.imageio.ImageIO;
030import javax.imageio.ImageTypeSpecifier;
031import javax.imageio.ImageWriteParam;
032import javax.imageio.ImageWriter;
033import javax.imageio.stream.ImageOutputStream;
034
035import net.gdface.utils.Assert;
036
037/**
038 * 图像工具类
039 * @author guyadong
040 *
041 */
042public class ImageUtil {
043
044        /**
045         * 对图像进行缩放
046         * @param source 原图
047         * @param targetWidth 缩放后图像宽度
048         * @param targetHeight 缩放后图像高度
049         * @param constrain 为true时等比例缩放,targetWidth,targetHeight为缩放图像的限制尺寸
050         * @return
051         */
052        public static BufferedImage resize(BufferedImage source, int targetWidth, int targetHeight,boolean constrain) {
053                if(constrain){
054                        double aspectRatio = (double)source.getWidth()/source.getHeight();
055                        double sx = (double) targetWidth / source.getWidth();
056                        double sy = (double) targetHeight / source.getHeight();
057                        if (sx > sy) {
058                                targetWidth = (int) Math.round(targetHeight*aspectRatio);
059                        } else {
060                                targetHeight = (int) Math.round(targetWidth/aspectRatio);
061                        }       
062                }
063                int type = source.getType();
064                BufferedImage target = null;
065                if (type == BufferedImage.TYPE_CUSTOM) {
066                        ColorModel cm = source.getColorModel();
067                        WritableRaster raster = cm.createCompatibleWritableRaster(targetWidth, targetHeight);
068                        target = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
069                } else {
070                        target = new BufferedImage(targetWidth, targetHeight, type);
071                }
072                Graphics2D g = target.createGraphics();
073                try{
074                        g.drawImage(  
075                                        source.getScaledInstance(targetWidth, targetHeight, Image.SCALE_SMOOTH),  
076                                        0, 0, null);
077                }finally{
078                        g.dispose();
079                }
080                return target;
081        }
082        public static byte[] wirteJPEGBytes(BufferedImage source){
083                return wirteJPEGBytes(source,null);
084        }
085        public static byte[] wirteBMPBytes(BufferedImage source){
086                return wirteBytes(source,"BMP");
087        }
088        public static byte[] wirtePNGBytes(BufferedImage source){
089                return wirteBytes(source,"PNG");
090        }
091        public static byte[] wirteGIFBytes(BufferedImage source){
092                return wirteBytes(source,"GIF");
093        }
094        /**
095         * 将原图压缩生成jpeg格式的数据
096         * @param source
097         * @return
098         * @see #wirteBytes(BufferedImage, String)
099         */
100        public static byte[] wirteJPEGBytes(BufferedImage source,Float compressionQuality){
101                return wirteBytes(source,"JPEG",compressionQuality);
102        }
103        public static byte[] wirteBytes(BufferedImage source,String formatName){
104                return wirteBytes(source,formatName,null);
105        }
106        /**
107         * 将{@link BufferedImage}生成formatName指定格式的图像数据
108         * @param source
109         * @param formatName 图像格式名,图像格式名错误则抛出异常,可用的值 'BMP','PNG','GIF','JPEG'
110         * @param compressionQuality 压缩质量(0.0~1.0),超过此范围抛出异常,为null使用默认值
111         * @return
112         */
113        public static byte[] wirteBytes(BufferedImage source,String formatName,Float compressionQuality){
114                ByteArrayOutputStream output = new ByteArrayOutputStream();
115                try {
116                        wirte(source, formatName, compressionQuality, output);
117                } catch (IOException e) {
118                        throw new RuntimeException(e);
119                }
120                return output.toByteArray();            
121        }
122        /**
123         * 将{@link BufferedImage}生成formatName指定格式的图像数据
124         * @param source
125         * @param formatName 图像格式名,图像格式名错误则抛出异常,可用的值 'BMP','PNG','GIF','JPEG'
126         * @param compressionQuality 压缩质量(0.0~1.0),超过此范围抛出异常,为null使用默认值
127         * @param output 输出流
128         * @throws IOException 
129         */
130        public static void wirte(BufferedImage source,String formatName,Float compressionQuality,OutputStream output) throws IOException{
131                Assert.notNull(source, "source");
132                Assert.notEmpty(formatName, "formatName");
133                Assert.notNull(output, "output");
134
135                Graphics2D g = null;
136                try {
137                        // 对于某些格式的图像(如png),直接调用ImageIO.write生成jpeg可能会失败
138                        // 所以先尝试直接调用ImageIO.write,如果失败则用Graphics生成新的BufferedImage再调用ImageIO.write
139                        for(BufferedImage s=source;!write(s, formatName, output,compressionQuality);){
140                                if(null!=g){
141                                        throw new IllegalArgumentException(String.format("not found writer for '%s'",formatName));
142                                }
143                                s = new BufferedImage(source.getWidth(),
144                                                source.getHeight(), BufferedImage.TYPE_INT_RGB);
145                                g = s.createGraphics();
146                                g.drawImage(source, 0, 0,null);                         
147                        }                               
148                } finally {
149                        if (null != g){
150                                g.dispose();
151                        }
152                }
153        }
154        /**
155         * 对原图创建缩略图对象<br>
156         * 如果原图尺寸小于指定的缩略图尺寸则直接返回原图对象的副本
157         * @param source 原图对象
158         * @param thumbnailWidth 缩略图宽度
159         * @param thumbnailHeight 缩略图高度
160         * @param ratioThreshold 最大宽高比阀值(宽高中较大的值/较小的值),<此值时对原图等比例缩放,>=此值时从原图切出中间部分图像再等比例缩放
161         * @return
162         */
163        public static BufferedImage createThumbnail(BufferedImage source,int thumbnailWidth,int thumbnailHeight,double ratioThreshold) {
164                int w = source.getWidth();
165                int h = source.getHeight();
166                if (w < thumbnailWidth && h < thumbnailHeight) {
167                        // 返回原图的副本
168                        return source.getSubimage(0, 0, w, h);
169                }
170                double thumAspectRatio = (double) thumbnailWidth / thumbnailHeight;
171                double wh_sca = w > h ? (double) w / h : (double) h / w;
172                if (wh_sca >= ratioThreshold) {
173                        if (w > h) {
174                                int fw = (int) (thumAspectRatio * h);
175                                if (h <= thumbnailHeight) {
176                                        return source.getSubimage((w - fw) / 2, 0, fw, h);
177                                } else {
178                                        return resize(source.getSubimage( (w - fw) / 2, 0, fw, h), thumbnailWidth,
179                                                        thumbnailHeight,true);
180                                }
181                        } else {
182                                int fh = (int) (thumAspectRatio * w);
183                                if (w <= thumbnailWidth) {
184                                        return source.getSubimage( 0, (h - fh) / 2, w, fh);
185                                } else {
186                                        return resize(source.getSubimage( 0, (h - fh) / 2, w, fh), thumbnailWidth,
187                                                        thumbnailHeight,true);
188                                }
189                        }
190                } else {
191                        return resize(source, thumbnailWidth, thumbnailHeight,true);
192                }
193        }
194        /**
195         * 对原图创建JPEG格式的缩略图
196         * @param imageBytes 图像数据字节数组
197         * @param thumbnailWidth
198         * @param thumbnailHeight
199         * @param ratioThreshold
200         * @return 返回jpeg格式的图像数据字节数组
201         * @see #createThumbnail(BufferedImage, int, int, double)
202         * @see #wirteJPEGBytes(BufferedImage)
203         */
204        public static byte[] createJPEGThumbnail(byte[] imageBytes,int thumbnailWidth,int thumbnailHeight,double ratioThreshold) {
205                Assert.notEmpty(imageBytes, "imageBytes");
206                try {
207                        BufferedImage source = ImageIO.read(new ByteArrayInputStream(imageBytes));
208                        if(null==source){
209                                throw new IllegalArgumentException("unsupported image format");
210                        }
211                        BufferedImage thumbnail = createThumbnail(source, thumbnailWidth, thumbnailHeight, ratioThreshold);
212                        return wirteJPEGBytes(thumbnail);
213                } catch (IOException e) {
214                        throw new RuntimeException(e);
215                }               
216        }
217
218        /**
219         * @param image
220         * @param bandOffset 用于判断通道顺序
221         * @return
222         */
223        private static boolean equalBandOffsetWith3Byte(BufferedImage image,int[] bandOffset){
224                if(image.getType()==BufferedImage.TYPE_3BYTE_BGR){
225                        if(image.getData().getSampleModel() instanceof ComponentSampleModel){
226                                ComponentSampleModel sampleModel = (ComponentSampleModel)image.getData().getSampleModel();
227                                if(Arrays.equals(sampleModel.getBandOffsets(), bandOffset)){
228                                        return true;
229                                }
230                        }
231                }
232                return false;           
233        }
234        public static boolean isBGRA(BufferedImage image){
235                return image.getType()==BufferedImage.TYPE_4BYTE_ABGR
236                                        || image.getType()==BufferedImage.TYPE_4BYTE_ABGR_PRE;                  
237        }
238        public static boolean isGray(BufferedImage image){
239                return image.getType()==BufferedImage.TYPE_BYTE_GRAY;                   
240        }
241        public static boolean isBGR3Byte(BufferedImage image){
242                return equalBandOffsetWith3Byte(image,new int[]{0, 1, 2});
243        }
244
245        public static boolean isRGB3Byte(BufferedImage image){
246                return equalBandOffsetWith3Byte(image,new int[]{2, 1, 0});
247        }
248
249        /**
250         * 对图像解码返回RGB格式矩阵数据
251         * @param image
252         * @return 
253         */
254        public static byte[] getMatrixRGB(BufferedImage image) {
255                if(null==image){
256                        throw new NullPointerException();
257                }
258                byte[] matrixRGB;
259                if(isRGB3Byte(image)){
260                        matrixRGB= (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
261                }else{
262                        // 转RGB格式
263                        BufferedImage rgbImage = new BufferedImage(image.getWidth(), image.getHeight(),  
264                        BufferedImage.TYPE_3BYTE_BGR);
265                        new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(image, rgbImage);
266                        matrixRGB= (byte[]) rgbImage.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
267                } 
268                return matrixRGB;
269        }
270        /**
271         * 对图像解码返回RGB格式矩阵数据
272         * @param image
273         * @return 
274         */
275        public static byte[] getMatrixRGBA(BufferedImage image) {
276                if(null==image){
277                        throw new NullPointerException();
278                }
279                byte[] matrixRGBA;
280                if(isBGRA(image)){
281                        matrixRGBA= (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
282                }else{
283                        // 转RGBA格式
284                        BufferedImage rgbaImage = new BufferedImage(image.getWidth(), image.getHeight(),  
285                        BufferedImage.TYPE_4BYTE_ABGR);
286                        new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(image, rgbaImage);
287                        matrixRGBA= (byte[]) rgbaImage.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
288                } 
289                return matrixRGBA;
290        }
291
292        /**
293         * 对图像解码返回BGR格式矩阵数据
294         * @param image
295         * @return
296         */
297        public static byte[] getMatrixBGR(BufferedImage image){
298                if(null==image){
299                        throw new NullPointerException();
300                }
301                byte[] matrixBGR;
302                if(isBGR3Byte(image)){
303                        matrixBGR= (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
304                }else{                  
305                        // ARGB格式图像数据
306                        int intrgb[]=image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
307                        matrixBGR=new byte[image.getWidth() * image.getHeight()*3];
308                        // ARGB转BGR格式
309                        for(int i=0,j=0;i<intrgb.length;++i,j+=3){
310                                matrixBGR[j]=(byte) (intrgb[i]&0xff);
311                                matrixBGR[j+1]=(byte) ((intrgb[i]>>8)&0xff);
312                                matrixBGR[j+2]=(byte) ((intrgb[i]>>16)&0xff);
313                        }
314                } 
315                return matrixBGR;
316        }
317        /**
318         * 对图像解码返回BGR格式矩阵数据
319         * @param image
320         * @return
321         */
322        public static byte[] getMatrixGRAY(BufferedImage image){
323                if(null==image){
324                        throw new NullPointerException();
325                }
326                byte[] matrixGray;
327                if(isGray(image)){
328                        matrixGray= (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
329                }else{                  
330                        // 图像转灰
331                        BufferedImage gray = new BufferedImage(image.getWidth(), image.getHeight(),  
332                        BufferedImage.TYPE_BYTE_GRAY);
333                        new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null).filter(image, gray);
334                    matrixGray= (byte[]) gray.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);       
335                        
336                } 
337                return matrixGray;
338        }
339        
340        /**
341         * 从源{@link BufferedImage}对象创建一份拷贝
342         * @param src
343         * @param imageType 创建的{@link BufferedImage}目标对象类型
344         * @return 返回拷贝的对象
345         * @see BufferedImage#BufferedImage(int, int, int)
346         */
347        public static  BufferedImage copy(Image src,int imageType){
348                if(null==src){
349                        throw new NullPointerException("src must not be null");
350                }
351                BufferedImage dst = new BufferedImage(src.getWidth(null), src.getHeight(null),  imageType);
352                Graphics g = dst.getGraphics();
353                try{
354                        g.drawImage(src, 0, 0, null);
355                        return dst;
356                }finally{
357                        g.dispose();
358                }
359        }
360        /**
361         * 创建{@link BufferedImage#TYPE_3BYTE_BGR}类型的拷贝
362         * @param src
363         * @return 返回拷贝的对象
364         * @see #copy(Image, int)
365         */
366        public static  BufferedImage copy(BufferedImage src){
367                return copy(src,BufferedImage.TYPE_3BYTE_BGR);
368        }
369        /**
370         * 对原图缩放,返回缩放后的新对象
371         * @param src
372         * @param scale
373         * @return 返回缩放后的新对象
374         */
375        public static BufferedImage scale(BufferedImage src,double scale){
376                if(null==src){
377                        throw new NullPointerException("src must not be null");
378                }
379                if(0>=scale){
380                        throw new IllegalArgumentException("scale must >0");
381                }
382                int width = src.getWidth();        // 源图宽   
383                int height = src.getHeight();        // 源图高 
384        Image image = src.getScaledInstance((int)Math.round(width * scale), (int)Math.round(height * scale), Image.SCALE_SMOOTH);
385        return copy(image,BufferedImage.TYPE_3BYTE_BGR);
386        }
387        /**
388         * 将{@link Image}图像上下左右扩充指定的尺寸
389         * @param src
390         * @param imageType
391         * @param left
392         * @param top
393         * @param right
394         * @param bottom
395         * @return 返回扩展尺寸后的新对象
396         */
397        public static BufferedImage growCanvas(Image src,int imageType,int left,int top,int right,int bottom){
398                if(null==src){
399                        throw new NullPointerException("src must not be null");
400                }
401                if(left<0||top<0||right<0||bottom<0){
402                        throw new IllegalArgumentException("left,top,right,bottom must >=0");
403                }
404                BufferedImage dst = new BufferedImage(src.getWidth(null)+left+right, src.getHeight(null)+top+bottom,  imageType);
405                Graphics g = dst.getGraphics();
406                try{
407                        g.setColor(Color.BLACK);
408                        g.fillRect(0,0, dst.getWidth(), dst.getHeight());
409                        g.drawImage(src, left, top, null);
410                        return dst;
411                }finally{
412                        g.dispose();
413                }
414        }
415        /**
416         * @param src
417         * @param left
418         * @param top
419         * @param right
420         * @param bottom
421         * @return
422         * @see #growCanvas(Image, int, int, int, int, int)
423         */
424        public static BufferedImage growCanvas(Image src,int left,int top,int right,int bottom){
425                return growCanvas(src,BufferedImage.TYPE_3BYTE_BGR,left,top,right,bottom);
426        }
427        /**
428         * 将{@link Image}图像(向右下)扩充为正文形(尺寸长宽最大边)
429         * @param src
430         * @return
431         */
432        public static BufferedImage growSquareCanvas(Image src){
433                if(null==src){
434                        throw new NullPointerException("src must not be null");
435                }
436                int width=src.getWidth(null);
437                int height=src.getHeight(null);
438                int size=Math.max(width, height);
439                return growCanvas(src,BufferedImage.TYPE_3BYTE_BGR,0,0,size-width,size-height);
440        }
441    /**
442     * Returns <code>ImageWriter</code> instance according to given
443     * rendered image and image format or <code>null</code> if there
444     * is no appropriate writer.
445     */
446    private static ImageWriter getImageWriter(RenderedImage im,
447                                         String formatName) {
448        ImageTypeSpecifier type =
449            ImageTypeSpecifier.createFromRenderedImage(im);
450        Iterator<ImageWriter> iter = ImageIO.getImageWriters(type, formatName);
451
452        if (iter.hasNext()) {
453            return iter.next();
454        } else {
455            return null;
456        }
457    }
458
459        /**
460         * 将原图压缩生成{@code formatName}指定格式的数据<br>
461         * 除了可以指定生成的图像质量之外,
462         * 其他行为与{@link ImageIO#write(RenderedImage, String, OutputStream)}相同
463         * @param source
464         * @param formatName
465         * @param output
466         * @param compressionQuality 指定图像质量,为{@code null}调用{@link ImageIO#write(RenderedImage, String, OutputStream)}
467         * @return
468         * @throws IOException
469         */
470        public static boolean write(RenderedImage source,
471                        String formatName,
472                        OutputStream output,
473                        Float compressionQuality) throws IOException{
474                if(null == compressionQuality){
475                        return ImageIO.write(source, formatName, output);
476                }
477                ImageWriter writer = getImageWriter(source,formatName);
478                if(null == writer){
479                        return false;
480                }
481        ImageOutputStream stream = null;
482        try {
483            stream = ImageIO.createImageOutputStream(output);
484        } catch (IOException e) {
485            throw new IIOException("Can't create output stream!", e);
486        }
487                writer.setOutput(stream);
488                ImageWriteParam param = writer.getDefaultWriteParam();
489                try{
490                        if(param.canWriteCompressed()){
491                                try{
492                                        param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
493                                        param.setCompressionQuality(compressionQuality);
494                                }catch(RuntimeException e){                                     
495                                }
496                        }
497                        writer.write(null, new IIOImage(source, null, null), param);
498                        return true;
499                } finally {
500                        writer.dispose();
501                        stream.flush();
502                }
503        }
504        /**
505         * 从RGB格式图像矩阵数据创建一个BufferedImage
506         * @param matrixRGB RGB格式图像矩阵数据,为null则创建一个指定尺寸的空图像
507         * @param width
508         * @param height
509         * @return
510         */
511        public static BufferedImage createRGBImage(byte[] matrixRGB,int width,int height){
512                int bytePerPixel = 3;
513                Assert.isTrue(null==matrixRGB || matrixRGB.length==width*height*bytePerPixel,"invalid image argument");
514            DataBufferByte dataBuffer = null==matrixRGB ? null : new DataBufferByte(matrixRGB, matrixRGB.length);
515            ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
516            int[] bOffs = {0, 1, 2};
517            ComponentColorModel colorModel = new ComponentColorModel(cs, false, false,
518                                                 Transparency.OPAQUE,
519                                                 DataBuffer.TYPE_BYTE);            
520            WritableRaster raster = null != dataBuffer
521                        ? Raster.createInterleavedRaster(dataBuffer, width, height, width*bytePerPixel, bytePerPixel, bOffs, null)
522                        : Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height,width*bytePerPixel, bytePerPixel, bOffs, null);
523            BufferedImage img = new BufferedImage(colorModel,raster,colorModel.isAlphaPremultiplied(),null);
524            return  img;            
525        }
526        /**
527         * 从RGBA格式图像矩阵数据创建一个BufferedImage<br>
528         * 该方法删除了alpha通道
529         * @param matrixRGBA RGBA格式图像矩阵数据,为null则创建一个指定尺寸的空图像
530         * @param width
531         * @param height
532         * @return
533         */
534        public static BufferedImage createRGBAImage(byte[] matrixRGBA,int width,int height){
535                int bytePerPixel = 4;
536                Assert.isTrue(null==matrixRGBA || matrixRGBA.length==width*height*bytePerPixel,"invalid image argument");
537            DataBufferByte dataBuffer = null==matrixRGBA ? null : new DataBufferByte(matrixRGBA, matrixRGBA.length);
538            ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
539            int[] bOffs = {0, 1, 2};
540            ComponentColorModel colorModel = new ComponentColorModel(cs, false, false,
541                                                 Transparency.OPAQUE,
542                                                 DataBuffer.TYPE_BYTE);            
543            WritableRaster raster = null != dataBuffer
544                        ? Raster.createInterleavedRaster(dataBuffer, width, height, width*bytePerPixel, bytePerPixel, bOffs, null)
545                        : Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height,width*bytePerPixel, bytePerPixel, bOffs, null);
546            BufferedImage img = new BufferedImage(colorModel,raster,colorModel.isAlphaPremultiplied(),null);
547            return  img;
548        }
549        private static void assertContains(final Rectangle parent, String argParent, final Rectangle sub, final String argSub)
550                        throws IllegalArgumentException {
551                if(!parent.contains(sub))
552                        throw new IllegalArgumentException(String.format(
553                                "the %s(X%d,Y%d,W%d,H%d) not contained by %s(X%d,Y%d,W%d,H%d)",
554                                argSub,sub.x, sub.y,sub.width, sub.height, argParent,parent.x,parent.y,parent.width, parent.height));
555        }
556        /**
557         * 从matrix矩阵中截取rect指定区域的子矩阵
558         * @param matrix 3byte(RGB/BGR) 图像矩阵
559         * @param matrixRect 矩阵尺寸
560         * @param rect 截取区域
561         * @return 
562         */
563        public static byte[] cutMatrix(byte[] matrix,Rectangle matrixRect,Rectangle rect) {
564                // 解码区域,为null或与图像尺寸相等时直接返回 matrix
565                if((rect == null || rect.equals(matrixRect)))
566                        return matrix;
567                else{
568                        // 如果指定的区域超出图像尺寸,则抛出异常
569                        ImageUtil.assertContains(matrixRect, "srcRect", rect ,"rect");
570                        byte[] dstArray=new byte[rect.width*rect.height*3];     
571                        // 从 matrix 中复制指定区域的图像数据返回
572                        for(int dstIndex=0,srcIndex=(rect.y*matrixRect.width+rect.x)*3,y=0;
573                                        y<rect.height;
574                                        ++y,srcIndex+=matrixRect.width*3,dstIndex+=rect.width*3){
575                                // 调用 System.arrayCopy每次复制一行数据
576                                System.arraycopy(matrix, srcIndex, dstArray, dstIndex, rect.width*3);
577                        }
578                        return dstArray;
579                }
580        }
581}