基于图像模块实现图库图片的四种常见操作开发分享 精华

奶盖
发布于 2021-3-17 15:07
浏览
2收藏

1. 项目介绍

 

HarmonyOS图像模块支持图像业务的开发,常见功能如图像解码、图像编码、基本的位图操作、图像编辑等。当然,也支持通过接口组合来实现更复杂的图像处理逻辑。本教程以图库图片中旋转、剪裁、缩放、镜像四种常见操作为例,给大家介绍HarmonyOS图像编解码的相关开发指导。

 

2. 将图片转换为PixelMap

 

图像解码就是将所支持格式的存档图片解码成统一的PixelMap图像,用于后续图像显示或其他处理,比如旋转、缩放、剪裁等。当前支持格式包括JPEG、PNG、GIF、HEIF、WebP、BMP。本例为您提供了getPixelMapFromResource函数,可以将resources/base/media目录下的图片资源转换为PixelMap图像,其中入参为图片的资源ID。

 

private PixelMap getPixelMapFromResource(int resourceId) {
    InputStream inputStream = null;
    try {
        // 创建图像数据源ImageSource对象
        inputStream = getContext().getResourceManager().getResource(resourceId);
        ImageSource.SourceOptions srcOpts = new ImageSource.SourceOptions();
        srcOpts.formatHint = "image/jpg";
        ImageSource imageSource = ImageSource.create(inputStream, srcOpts);


        // 设置图片参数
        ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
        return imageSource.createPixelmap(decodingOptions);
    } catch (IOException e) {
        HiLog.info(LABEL_LOG, "IOException");
    } catch (NotExistException e) {
        HiLog.info(LABEL_LOG, "NotExistException");
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                HiLog.info(LABEL_LOG, "inputStream IOException");
            }
        }
    }
    return null;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

 

3. 图片参数设置

 

本例使用图片像素的尺寸为1024*768,点击一次旋转按钮会进行90度的旋转,缩放是按照2:1的比例进行缩放,剪裁是保证宽度不变的情况下对高度进行400像素的剪裁,相关参数设置如下所示:

 

// 设置图片参数 
ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions(); 
// 旋转 
decodingOptions.rotateDegrees = 90 * whirlCount; 
// 缩放 
decodingOptions.desiredSize = new Size(isScale ? 512 : 0, isScale ? 384 : 0); 
// 剪裁 
decodingOptions.desiredRegion = new Rect(0, 0, isCorp ? 1024 : 0, isCorp ? 400 : 0);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

 

4. 图片镜像操作


图片镜像操作就是对图片以纵坐标为轴制作对称图片。image绘制的时候会调用onDraw方法,本例采用对图像Canvas画布的镜像操作实现图片的镜像显示,示例代码如下所示:

 

private void mirrorImage(PixelMap pixelMap) { 
    scaleX = -scaleX; 
    image.addDrawTask( 
            new Component.DrawTask() { 
                @Override 
                public void onDraw(Component component, Canvas canvas) { 
                    if (isMirror) { 
                        isMirror = false; 
                        PixelMapHolder pmh = new PixelMapHolder(pixelMap); 
                        canvas.scale( 
                                scaleX, 
                                1.0f, 
                                (float) pixelMap.getImageInfo().size.width / 2, 
                                (float) pixelMap.getImageInfo().size.height / 2); 
                        canvas.drawPixelMapHolder( 
                                pmh, 
                                0, 
                                0, 
                                new Paint()); 
                    } 
                } 
            }); 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

 

5. 完整示例


以手机为例,初始化页面如图1所示,依次点击按钮可以实现图片的旋转、剪裁、缩放、镜像,效果如下所示(您需要准备一张像素尺寸为1024*768的图片,放到ImageDemo\entry\src\main\resources\base\media目录下):

 

基于图像模块实现图库图片的四种常见操作开发分享-鸿蒙开发者社区

 

示例代码如下:

 

import com.huawei.codelab.ResourceTable; 
 
import ohos.aafwk.ability.AbilitySlice; 
import ohos.aafwk.content.Intent; 
import ohos.agp.components.Button; 
import ohos.agp.components.Component; 
import ohos.agp.components.Image; 
import ohos.agp.render.Canvas; 
import ohos.agp.render.Paint; 
import ohos.agp.render.PixelMapHolder; 
import ohos.global.resource.NotExistException; 
import ohos.hiviewdfx.HiLog; 
import ohos.hiviewdfx.HiLogLabel; 
import ohos.media.image.ImageSource; 
import ohos.media.image.PixelMap; 
import ohos.media.image.common.PixelFormat; 
import ohos.media.image.common.Rect; 
import ohos.media.image.common.Size; 
 
import java.io.IOException; 
import java.io.InputStream; 
 
/** 
 * 图像主页面 
 */ 
public class MainAbilitySlice extends AbilitySlice { 
    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "MainAbilitySlice"); 
    Image image; 
    PixelMap imagePixelMap; 
    Button whirlImageBtn; 
    Button cropImageBtn; 
    Button scaleImageBtn; 
    Button mirrorImageBtn; 
    private int whirlCount = 0; 
    private boolean isCorp = false; 
    private boolean isScale = false; 
    private boolean isMirror = false; 
    private float scaleX = 1.0f; 
 
    @Override 
    public void onStart(Intent intent) { 
        super.onStart(intent); 
        super.setUIContent(ResourceTable.Layout_ability_main); 
        initView(); 
    } 
 
    private void initView() { 
        if (findComponentById(ResourceTable.Id_whirl_image) instanceof Button) { 
            whirlImageBtn = (Button) findComponentById(ResourceTable.Id_whirl_image); 
        } 
        if (findComponentById(ResourceTable.Id_crop_image) instanceof Button) { 
            cropImageBtn = (Button) findComponentById(ResourceTable.Id_crop_image); 
        } 
        if (findComponentById(ResourceTable.Id_scale_image) instanceof Button) { 
            scaleImageBtn = (Button) findComponentById(ResourceTable.Id_scale_image); 
        } 
        if (findComponentById(ResourceTable.Id_mirror_image) instanceof Button) { 
            mirrorImageBtn = (Button) findComponentById(ResourceTable.Id_mirror_image); 
        } 
        if (findComponentById(ResourceTable.Id_image) instanceof Image) { 
            image = (Image) findComponentById(ResourceTable.Id_image); 
        } 
        whirlImageBtn.setClickedListener(new ButtonClick()); 
        cropImageBtn.setClickedListener(new ButtonClick()); 
        scaleImageBtn.setClickedListener(new ButtonClick()); 
        mirrorImageBtn.setClickedListener(new ButtonClick()); 
    } 
 
    private class ButtonClick implements Component.ClickedListener { 
        @Override 
        public void onClick(Component component) { 
            int btnId = component.getId(); 
            switch (btnId) { 
                case ResourceTable.Id_whirl_image: 
                    // 旋转图片 
                    whirlCount++; 
                    isCorp = false; 
                    isScale = false; 
                    isMirror = false; 
                    imagePixelMap = getPixelMapFromResource(ResourceTable.Media_shanghai); 
                    image.setPixelMap(imagePixelMap); 
                    break; 
                case ResourceTable.Id_crop_image: 
                    // 剪裁图片 
                    whirlCount = 0; 
                    isCorp = !isCorp; 
                    isScale = false; 
                    isMirror = false; 
                    imagePixelMap = getPixelMapFromResource(ResourceTable.Media_shanghai); 
                    image.setPixelMap(imagePixelMap); 
                    break; 
                case ResourceTable.Id_scale_image: 
                    // 缩放图片 
                    whirlCount = 0; 
                    isCorp = false; 
                    isScale = !isScale; 
                    isMirror = false; 
                    imagePixelMap = getPixelMapFromResource(ResourceTable.Media_shanghai); 
                    image.setPixelMap(imagePixelMap); 
                    break; 
                case ResourceTable.Id_mirror_image: 
                    // 镜像图片 
                    whirlCount = 0; 
                    isCorp = false; 
                    isScale = false; 
                    isMirror = true; 
                    imagePixelMap = getPixelMapFromResource(ResourceTable.Media_shanghai); 
                    mirrorImage(imagePixelMap); 
                    image.setPixelMap(imagePixelMap); 
                    break; 
                default: 
                    break; 
            } 
        } 
    } 
 
    private void mirrorImage(PixelMap pixelMap) { 
        scaleX = -scaleX; 
        image.addDrawTask( 
                new Component.DrawTask() { 
                    @Override 
                    public void onDraw(Component component, Canvas canvas) { 
                        if (isMirror) { 
                            isMirror = false; 
                            PixelMapHolder pmh = new PixelMapHolder(pixelMap); 
                            canvas.scale( 
                                    scaleX, 
                                    1.0f, 
                                    (float) pixelMap.getImageInfo().size.width / 2, 
                                    (float) pixelMap.getImageInfo().size.height / 2); 
                            canvas.drawPixelMapHolder( 
                                    pmh, 
                                    0, 
                                    0, 
                                    new Paint()); 
                        } 
                    } 
                }); 
    } 
 
    /** 
     * 通过图片ID返回PixelMap 
     * 
     * @param resourceId 图片的资源ID 
     * @return 图片的PixelMap 
     */ 
    private PixelMap getPixelMapFromResource(int resourceId) { 
        InputStream inputStream = null; 
        try { 
            // 创建图像数据源ImageSource对象 
            inputStream = getContext().getResourceManager().getResource(resourceId); 
            ImageSource.SourceOptions srcOpts = new ImageSource.SourceOptions(); 
            srcOpts.formatHint = "image/jpg"; 
            ImageSource imageSource = ImageSource.create(inputStream, srcOpts); 
 
            // 设置图片参数 
            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions(); 
            // 旋转 
            decodingOptions.rotateDegrees = 90 * whirlCount; 
            // 缩放 
            decodingOptions.desiredSize = new Size(isScale ? 512 : 0, isScale ? 384 : 0); 
            // 剪裁 
            decodingOptions.desiredRegion = new Rect(0, 0, isCorp ? 1024 : 0, isCorp ? 400 : 0); 
            decodingOptions.desiredPixelFormat = PixelFormat.ARGB_8888; 
            return imageSource.createPixelmap(decodingOptions); 
        } catch (IOException e) { 
            HiLog.info(LABEL_LOG, "IOException"); 
        } catch (NotExistException e) { 
            HiLog.info(LABEL_LOG, "NotExistException"); 
        } finally { 
            if (inputStream != null) { 
                try { 
                    inputStream.close(); 
                } catch (IOException e) { 
                    HiLog.info(LABEL_LOG, "inputStream IOException"); 
                } 
            } 
        } 
        return null; 
    } 
 
    @Override 
    public void onActive() { 
        super.onActive(); 
    } 
 
    @Override 
    public void onForeground(Intent intent) { 
        super.onForeground(intent); 
    } 
} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.

 

布局代码如下:

 

<?xml version="1.0" encoding="utf-8"?> 
<DirectionalLayout 
    xmlns:ohos="http://schemas.huawei.com/res/ohos" 
    ohos:height="match_parent" 
    ohos:width="match_parent" 
    ohos:orientation="vertical"> 
 
    <Text 
        ohos:height="match_content" 
        ohos:width="match_content" 
        ohos:layout_alignment="horizontal_center" 
        ohos:text="HarmonyOS图像开发" 
        ohos:text_size="80" 
        ohos:top_margin="40vp" 
        /> 
 
    <DirectionalLayout 
        xmlns:ohos="http://schemas.huawei.com/res/ohos" 
        ohos:height="match_content" 
        ohos:width="match_content" 
        ohos:layout_alignment="horizontal_center" 
        ohos:orientation="horizontal" 
        ohos:top_margin="20vp"> 
 
        <Button 
            ohos:id="$+id:whirl_image" 
            ohos:height="match_content" 
            ohos:width="match_content" 
            ohos:background_element="$graphic:background_button" 
            ohos:padding="12vp" 
            ohos:right_margin="5vp" 
            ohos:text="旋转" 
            ohos:text_size="20vp" 
            ohos:top_margin="10vp"> 
        </Button> 
 
        <Button 
            ohos:id="$+id:crop_image" 
            ohos:height="match_content" 
            ohos:width="match_content" 
            ohos:background_element="$graphic:background_button" 
            ohos:left_margin="5vp" 
            ohos:padding="12vp" 
            ohos:text="剪裁" 
            ohos:text_size="20vp" 
            ohos:top_margin="10vp"> 
        </Button> 
 
        <Button 
            ohos:id="$+id:scale_image" 
            ohos:height="match_content" 
            ohos:width="match_content" 
            ohos:background_element="$graphic:background_button" 
            ohos:left_margin="5vp" 
            ohos:padding="12vp" 
            ohos:text="缩放" 
            ohos:text_size="20vp" 
            ohos:top_margin="10vp"> 
        </Button> 
 
        <Button 
            ohos:id="$+id:mirror_image" 
            ohos:height="match_content" 
            ohos:width="match_content" 
            ohos:background_element="$graphic:background_button" 
            ohos:left_margin="5vp" 
            ohos:padding="12vp" 
            ohos:text="镜像" 
            ohos:text_size="20vp" 
            ohos:top_margin="10vp"/> 
    </DirectionalLayout> 
 
    <Image 
        ohos:id="$+id:image" 
        ohos:height="match_content" 
        ohos:width="match_content" 
        ohos:image_src="$media:shanghai.jpg" 
        ohos:layout_alignment="horizontal_center" 
        ohos:top_margin="20vp"> 
    </Image> 
 
</DirectionalLayout>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.

 

此外您还需在resource/base/graphic目录下添加background_button.xml

 

<?xml version="1.0" encoding="utf-8"?> 
<shape xmlns:ohos="http://schemas.huawei.com/res/ohos" 
       ohos:shape="rectangle"> 
    <corners 
        ohos:radius="40"/> 
    <solid 
        ohos:color="#e9e9e9"/> 
</shape>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

 

说明

以上代码仅demo演示参考使用,产品化的代码需要考虑数据校验和国际化。

 

 

@文章转载自HUAWEI Codelabs

分类
已于2022-5-5 14:25:07修改
1
收藏 2
回复
举报
1
2
2
2条回复
按时间正序
/
按时间倒序
张荣超_九丘教育
张荣超_九丘教育

👍👍👍

回复
2021-3-17 23:00:19
霹雳冬瓜
霹雳冬瓜

您好,博主 

我一直有个疑问,类似android的相册列表中的缩略图该如何压缩?直接使用图片自带的缩略图,图片内容会有马赛克的感觉,不想系统相册那样会比较清晰。根据图片真实长宽和列表中image组件长宽计算出一个合适的缩放值inSampleSize,并设置给decodingOptions.sampleSize。结果,压缩出来的图片大小居然没变化。

   然后根据inSampleSize,将原图宽高同比缩小并设置给decodingOptions.desiredSize,发现压缩成功了,不过还是有马赛克的感觉。

   这块我该如何压缩,才能保证清晰,而且压缩后大小不会很大,可以存储在LruCache中进行缓存呢?

回复
2021-3-18 09:04:19


回复
    相关推荐