前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android开发笔记(九十四)图片的基本加工

Android开发笔记(九十四)图片的基本加工

作者头像
aqi00
发布2019-01-18 14:05:33
7040
发布2019-01-18 14:05:33
举报
文章被收录于专栏:老欧说安卓老欧说安卓

位图管理Bitmap

Android上的图形使用Drawable类,而位图管理则使用Bitmap类,java上与之对应的是awt包中的BufferedImage。Android开发中有需要对jpg、png文件进行加工的,都是操作Bitmap,下面是Bitmap类的常用方法说明: compress : 根据设定的位图格式与压缩质量,对图片进行压缩。 recycle : 回收位图对象资源。 createBitmap : 从源图片中裁剪一块位图区域。 createScaledBitmap : 根据设定的目标大小,对源图片进行缩放。 getByteCount : 获取位图的字节大小。 getWidth : 获取位图的宽度。 getHeight : 获取位图的高度。

图片读写

图片文件的读写,其实就是Bitmap对象与图片文件的转换操作,有关图片文件读写的说明参见《Android开发笔记(三十三)文本文件和图片文件的读写》,下面是图片文件读写的示例代码:

代码语言:javascript
复制
	public static void saveBitmap(String path, Bitmap bitmap) {
		try {
			BufferedOutputStream bos = new BufferedOutputStream(
					new FileOutputStream(path));
			bitmap.compress(Bitmap.CompressFormat.JPEG, 80, bos);
			bos.flush();
			bos.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static Bitmap openBitmap(String path) {
		Bitmap bitmap = null;
		try {
			BufferedInputStream bis = new BufferedInputStream(
					new FileInputStream(path));
			bitmap = BitmapFactory.decodeStream(bis);
			bis.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return bitmap;
	}

至于Bitmap与Drawable之间的转换,则是通过BitmapDrawable来完成,其中Bitmap转Drawable的代码例子如下:

代码语言:javascript
复制
Drawable mDrawable = new BitmapDrawable(getResources(), bitmap);

Drawable转Bitmap的代码例子如下:

代码语言:javascript
复制
Bitmap bitmap = ((BitmapDrawable)mDrawable).getBitmap();

图片加工

常用的图片加工操作有:图片压缩、调整大小、图片裁剪、图片旋转等等,其中图片旋转的介绍参见《Android开发笔记(九十九)圆形转盘》。下面是图片压缩、调整大小、图片裁剪的使用说明:

图片压缩

压缩算法主要有两个因素,一个是图片格式,另一个是压缩质量,图片压缩可调用compress方法来实现。下面是图片压缩的代码例子:

代码语言:javascript
复制
import java.util.Locale;
import java.util.Map;

import com.example.exmimage.dialog.FileSaveFragment;
import com.example.exmimage.dialog.FileSaveFragment.FileSaveCallbacks;
import com.example.exmimage.dialog.FileSelectFragment;
import com.example.exmimage.dialog.FileSelectFragment.FileSelectCallbacks;
import com.example.exmimage.util.BitmapUtil;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;

public class CompressActivity extends Activity implements OnClickListener
	,OnLongClickListener,FileSelectCallbacks,FileSaveCallbacks {
	
	private ImageView iv_compress;
	private EditText et_quality;
	private Drawable mDrawable = null;
	private String mExtesion;
	private int mQuality;

	private String[] extensionArray = {"jpg", "png"};
	class ExtensionSelectedListener implements OnItemSelectedListener {
		public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
			mExtesion = extensionArray[arg2];
		}

		public void onNothingSelected(AdapterView<?> arg0) {
		}
	}
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_compress);
		
		Button btn_open_drawable = (Button) findViewById(R.id.btn_open_drawable);
		Button btn_save_drawable = (Button) findViewById(R.id.btn_save_drawable);
		btn_open_drawable.setOnClickListener(this);
		btn_save_drawable.setOnClickListener(this);
		
		iv_compress = (ImageView) findViewById(R.id.iv_compress);
		et_quality = (EditText) findViewById(R.id.et_quality);
		et_quality.setOnLongClickListener(this);
		ArrayAdapter<String> extensionAdapter = new ArrayAdapter<String>(this,
				R.layout.spinner_item, extensionArray);
		extensionAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
		Spinner sp = (Spinner) findViewById(R.id.sp_format);
		sp.setPrompt("请选择图片格式");
		sp.setAdapter(extensionAdapter);
		sp.setOnItemSelectedListener(new ExtensionSelectedListener());
		sp.setSelection(0);
	}

	@Override
	public boolean onLongClick(View v) {
		if (v.getId() == R.id.et_quality) {
			et_quality.setText("");
		}
		return true;
	}

	@Override
	public void onClick(View v) {
		if (v.getId() == R.id.btn_open_drawable) {
			FileSelectFragment.show(this, new String[]{"jpg","png"}, null);
		} else if (v.getId() == R.id.btn_save_drawable) {
			if (mDrawable == null) {
				Toast.makeText(this, "请先打开图片文件", Toast.LENGTH_LONG).show();
				return;
			}
			mQuality = Integer.parseInt(et_quality.getText().toString());
			if (mQuality>100 || mQuality<10) {
				Toast.makeText(this, "图片质量有效值为10-100", Toast.LENGTH_LONG).show();
				return;
			}
			FileSaveFragment.show(this, mExtesion);
		}
	}

	@Override
	public boolean onCanSave(String absolutePath, String fileName) {
		return true;
	}

	@Override
	public void onConfirmSave(String absolutePath, String fileName) {
		String path = String.format("%s/%s", absolutePath, fileName);
		Bitmap bitmap = ((BitmapDrawable)mDrawable).getBitmap();
		BitmapUtil.saveBitmap(path, bitmap, mExtesion, mQuality);
		Toast.makeText(this, "成功保存图片文件:"+path, Toast.LENGTH_LONG).show();
	}

	@Override
	public void onConfirmSelect(String absolutePath, String fileName,
			Map<String, Object> map_param) {
		String extension = fileName.substring(fileName.lastIndexOf(".")+1);
		if (extension.toUpperCase(Locale.getDefault()).equals("PNG") == true) {
			mExtesion = "png";
		} else {
			mExtesion = "jpg";
		}
		String path = String.format("%s/%s", absolutePath, fileName);
		Bitmap bitmap = BitmapUtil.openBitmap(path);
		mDrawable = new BitmapDrawable(getResources(), bitmap);
		iv_compress.setImageDrawable(mDrawable);
		//这里也可以直接使用setImageBitmap
		//iv_compress.setImageBitmap(bitmap);
		//但是setBackground就只能用Drawable,不能用Bitmap了
		//iv_compress.setBackground(mDrawable);
	}

	@Override
	public boolean isFileValid(String absolutePath, String fileName,
			Map<String, Object> map_param) {
		return true;
	}

}

调整大小

调整图片大小可使用createScaledBitmap方法,该函数保留了图片的全貌,只做尺寸的缩小和放大。下面是调整图片大小的代码例子:

代码语言:javascript
复制
import java.util.Locale;
import java.util.Map;

import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.exmimage.dialog.FileSaveFragment;
import com.example.exmimage.dialog.FileSelectFragment;
import com.example.exmimage.dialog.FileSaveFragment.FileSaveCallbacks;
import com.example.exmimage.dialog.FileSelectFragment.FileSelectCallbacks;
import com.example.exmimage.util.BitmapUtil;

public class ResizeActivity extends Activity implements OnClickListener,
		OnLongClickListener, FileSelectCallbacks, FileSaveCallbacks {

	private ImageView iv_resize;
	private TextView tv_width, tv_height;
	private EditText et_width, et_height;
	private int mWidth, mHeight;
	private int mNewWidth, mNewHeight;
	private Bitmap mBitmap = null;
	private String mExtesion;
	private int mQuality = 100;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_resize);

		Button btn_open_resize = (Button) findViewById(R.id.btn_open_resize);
		Button btn_save_resize = (Button) findViewById(R.id.btn_save_resize);
		btn_open_resize.setOnClickListener(this);
		btn_save_resize.setOnClickListener(this);

		iv_resize = (ImageView) findViewById(R.id.iv_resize);
		tv_width = (TextView) findViewById(R.id.tv_width);
		tv_height = (TextView) findViewById(R.id.tv_height);
		et_width = (EditText) findViewById(R.id.et_width);
		et_width.setOnLongClickListener(this);
		et_width.addTextChangedListener(mWidthWatcher);
		et_height = (EditText) findViewById(R.id.et_height);
		et_height.setOnLongClickListener(this);
		et_height.addTextChangedListener(mHeightWatcher);
	}

	@Override
	public boolean onLongClick(View v) {
		if (v.getId() == R.id.et_width) {
			et_width.setText("");
		} else if (v.getId() == R.id.et_height) {
			et_height.setText("");
		}
		return true;
	}

	@Override
	public void onClick(View v) {
		if (v.getId() == R.id.btn_open_resize) {
			FileSelectFragment.show(this, new String[] { "jpg", "png" }, null);
		} else if (v.getId() == R.id.btn_save_resize) {
			if (mBitmap == null) {
				Toast.makeText(this, "请先打开图片文件", Toast.LENGTH_LONG).show();
				return;
			}
			mNewWidth = Integer.parseInt(et_width.getText().toString());
			mNewHeight = Integer.parseInt(et_height.getText().toString());
			if (mNewWidth<=0 || mNewHeight<=0) {
				Toast.makeText(this, "新图片的宽和高必须大于0", Toast.LENGTH_LONG)
						.show();
				return;
			}
			FileSaveFragment.show(this, mExtesion);
		}
	}

	@Override
	public boolean onCanSave(String absolutePath, String fileName) {
		return true;
	}

	@Override
	public void onConfirmSave(String absolutePath, String fileName) {
		String path = String.format("%s/%s", absolutePath, fileName);
		Bitmap bitmap = Bitmap.createScaledBitmap(mBitmap, mNewWidth, mNewHeight, false);
		BitmapUtil.saveBitmap(path, bitmap, mExtesion, mQuality);
		Toast.makeText(this, "成功保存图片文件:" + path, Toast.LENGTH_LONG).show();
	}

	@Override
	public void onConfirmSelect(String absolutePath, String fileName,
			Map<String, Object> map_param) {
		String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
		if (extension.toUpperCase(Locale.getDefault()).equals("PNG") == true) {
			mExtesion = "png";
		} else {
			mExtesion = "jpg";
		}
		String path = String.format("%s/%s", absolutePath, fileName);
		mBitmap = BitmapUtil.openBitmap(path);
		iv_resize.setImageBitmap(mBitmap);
		
		mWidth = mBitmap.getWidth();
		mHeight = mBitmap.getHeight();
		tv_width.setText(""+mWidth);
		tv_height.setText(""+mHeight);
	}

	@Override
	public boolean isFileValid(String absolutePath, String fileName,
			Map<String, Object> map_param) {
		return true;
	}
	
	private TextWatcher mWidthWatcher = new TextWatcher() {
		
		@Override
		public void beforeTextChanged(CharSequence s, int start, int count, int after) {
		}

		@Override
		public void onTextChanged(CharSequence s, int start, int before, int count) {
		}

		@Override
		public void afterTextChanged(Editable s) {
			float newWidth = Integer.parseInt(s.toString());
			et_height.removeTextChangedListener(mHeightWatcher);
			et_height.setText(""+(int)(newWidth/mWidth*mHeight));
			et_height.addTextChangedListener(mHeightWatcher);
		}

	};

	private TextWatcher mHeightWatcher = new TextWatcher() {
		
		@Override
		public void beforeTextChanged(CharSequence s, int start, int count, int after) {
		}

		@Override
		public void onTextChanged(CharSequence s, int start, int before, int count) {
		}

		@Override
		public void afterTextChanged(Editable s) {
			float newHeight = Integer.parseInt(s.toString());
			et_width.removeTextChangedListener(mWidthWatcher);
			et_width.setText(""+(int)(newHeight/mHeight*mWidth));
			et_width.addTextChangedListener(mWidthWatcher);
		}

	};

}

图片裁剪

裁剪图片有两种方法,一种是调用系统服务com.android.camera.action.CROP,该方法编码简单,但功能有限;另一种是自己写个裁剪算法,编码麻烦些,不过可定制实现复杂的功能。 下面是调用系统服务实现图片裁剪的代码例子(只列出了关键代码):

代码语言:javascript
复制
	@Override
	public void onClick(View v) {
		if (v.getId() == R.id.btn_open_system) {
			FileSelectFragment.show(this, new String[] { "jpg", "png" }, null);
		} else if (v.getId() == R.id.btn_save_system) {
			if (mNewBitmap == null) {
				Toast.makeText(this, "请先打开并裁剪图片文件", Toast.LENGTH_LONG).show();
				return;
			}
			FileSaveFragment.show(this, mExtesion);
		} else if (v.getId() == R.id.btn_cut_system) {
            Intent intent = new Intent("com.android.camera.action.CROP");
            intent.setDataAndType(Uri.parse("file://"+mOldFilePath), "image/*");
            // 设置裁剪
            intent.putExtra("crop", "true");
            // aspectX aspectY 是宽高的比例
            intent.putExtra("aspectX", 1);
            intent.putExtra("aspectY", 1);
            // outputX outputY 是裁剪图片宽高
            intent.putExtra("outputX", 300);
            intent.putExtra("outputY", 300);
            intent.putExtra("return-data", false);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.parse("file://"+mNewFilePath));
            intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
            startActivityForResult(intent, RESULT_REQUEST_CODE);
		}
	}
	
	@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            if (requestCode == RESULT_REQUEST_CODE) {
        		mNewBitmap = BitmapUtil.openBitmap(mNewFilePath);
        		iv_system_new.setImageBitmap(mNewBitmap);
            	//iv_system_new.setImageDrawable(Drawable.createFromPath(mNewFilePath));
            }
        }
    }

自己编码实现裁剪图片的话,一般是把图片分为两块区域,一块是裁剪的内部区域,需高亮显示;另一块位于裁剪区域外部,需阴影显示。这个编码似乎没有捷径,博主想到的办法是采用FrameLayout布局,内部放三个子视图,分别是: 1、原图片的ImageView; 2、阴影部分的View,裁剪开始时显示,裁剪结束后隐藏; 3、裁剪区域的ImageView,裁剪开始时显示,裁剪结束后隐藏; 这里实现的难点在于裁剪区域的ImageView,得基于ImageView自定义一种视图CropImageView。该视图的编码思路大致有三部分内容,首先,我们要按照设定的区域从原图片中截取一块位图出来,该功能可调用Bitmap的createBitmap方法来实现。其次,在手势按下时,根据当前按下的位置,判断接下来的裁剪动作,是拖动整个裁剪区域,还是移动某条边,还是移动某个角,这里一共要做十个判断(四条边、四个角、按在区域内部要拖动、按在区域外部不处理)。最后,重写onTouchEvent方法,在按下动作ACTION_DOWN时初始化触摸条件,在移动操作ACTION_MOVE时,根据裁剪动作刷新图片显示。 下面是自定义裁剪视图的效果截图:

下面是CropImageView的实现代码例子:

代码语言:javascript
复制
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;

public class CropImageView extends ImageView {

	public CropImageView(Context context) {
		super(context);
	}

	public CropImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public CropImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	private Bitmap mOrigBitmap = null;
	private Bitmap mCropBitmap = null;
	private Rect mRect = new Rect(0,0,0,0);
	
	public void setOrigBitmap(Bitmap orig) {
		mOrigBitmap = orig;
	}
	
	public Bitmap getOrigBitmap() {
		return mOrigBitmap;
	}

	public Bitmap getCropBitmap() {
		return mCropBitmap;
	}
	
	public boolean setBitmapRect(Rect rect) {
		if (mOrigBitmap == null) {
			return false;
		}
		if (rect.left<0 || rect.left>mOrigBitmap.getWidth()) {
			return false;
		}
		if (rect.top<0 || rect.top>mOrigBitmap.getHeight()) {
			return false;
		}
		if (rect.right<=0 || rect.left+rect.right>mOrigBitmap.getWidth()) {
			return false;
		}
		if (rect.bottom<=0 || rect.top+rect.bottom>mOrigBitmap.getHeight()) {
			return false;
		}
		mRect = rect;
		setPadding(mRect.left, mRect.top, 0, 0);
		mCropBitmap = Bitmap.createBitmap(mOrigBitmap, 
				mRect.left, mRect.top, mRect.right, mRect.bottom);
		setImageBitmap(mCropBitmap);
		postInvalidate();
		return true;
	}

	public Rect getBitmapRect() {
		return mRect;
	}
	
	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			mOriginX = event.getX();
			mOriginY = event.getY();
			mOriginRect = mRect;
			mDragMode = getDragMode(mOriginX, mOriginY);
			break;
		case MotionEvent.ACTION_MOVE:
			int offsetX = (int) (event.getX()-mOriginX);
			int offsetY = (int) (event.getY()-mOriginY);
			Rect rect = null;
			if (mDragMode == DRAG_NONE) {
				return true;
			} else if (mDragMode == DRAG_WHOLE) {
				rect = new Rect(mOriginRect.left+offsetX, mOriginRect.top+offsetY, mOriginRect.right, mOriginRect.bottom);
			} else if (mDragMode == DRAG_LEFT) {
				rect = new Rect(mOriginRect.left+offsetX, mOriginRect.top, mOriginRect.right-offsetX, mOriginRect.bottom);
			} else if (mDragMode == DRAG_RIGHT) {
				rect = new Rect(mOriginRect.left, mOriginRect.top, mOriginRect.right+offsetX, mOriginRect.bottom);
			} else if (mDragMode == DRAG_TOP) {
				rect = new Rect(mOriginRect.left, mOriginRect.top+offsetY, mOriginRect.right, mOriginRect.bottom-offsetY);
			} else if (mDragMode == DRAG_BOTTOM) {
				rect = new Rect(mOriginRect.left, mOriginRect.top, mOriginRect.right, mOriginRect.bottom+offsetY);
			} else if (mDragMode == DRAG_LEFT_TOP) {
				rect = new Rect(mOriginRect.left+offsetX, mOriginRect.top+offsetY, mOriginRect.right-offsetX, mOriginRect.bottom-offsetY);
			} else if (mDragMode == DRAG_RIGHT_TOP) {
				rect = new Rect(mOriginRect.left, mOriginRect.top+offsetY, mOriginRect.right+offsetX, mOriginRect.bottom-offsetY);
			} else if (mDragMode == DRAG_LEFT_BOTTOM) {
				rect = new Rect(mOriginRect.left+offsetX, mOriginRect.top, mOriginRect.right-offsetX, mOriginRect.bottom+offsetY);
			} else if (mDragMode == DRAG_RIGHT_BOTTOM) {
				rect = new Rect(mOriginRect.left, mOriginRect.top, mOriginRect.right+offsetX, mOriginRect.bottom+offsetY);
			}
			setBitmapRect(rect);
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
			break;
		default:
			break;
		}
		return true;
	}

	private int DRAG_NONE = 0;
	private int DRAG_WHOLE = 1;
	private int DRAG_LEFT = 2;
	private int DRAG_RIGHT = 3;
	private int DRAG_TOP = 4;
	private int DRAG_BOTTOM = 5;
	private int DRAG_LEFT_TOP = 6;
	private int DRAG_RIGHT_TOP = 7;
	private int DRAG_LEFT_BOTTOM = 8;
	private int DRAG_RIGHT_BOTTOM = 9;
	
	private int mDragMode = DRAG_NONE;
	private int mInterval = 10;
	private float mOriginX, mOriginY;
	private Rect mOriginRect;
	
	private int getDragMode(float f, float g) {
		int left = mRect.left;
		int top = mRect.top;
		int right = mRect.left + mRect.right;
		int bottom = mRect.top + mRect.bottom;
		if (Math.abs(f-left)<=mInterval && Math.abs(g-top)<=mInterval) {
			return DRAG_LEFT_TOP;
		} else if (Math.abs(f-right)<=mInterval && Math.abs(g-top)<=mInterval) {
			return DRAG_RIGHT_TOP;
		} else if (Math.abs(f-left)<=mInterval && Math.abs(g-bottom)<=mInterval) {
			return DRAG_LEFT_BOTTOM;
		} else if (Math.abs(f-right)<=mInterval && Math.abs(g-bottom)<=mInterval) {
			return DRAG_RIGHT_BOTTOM;
		} else if (Math.abs(f-left)<=mInterval && g>top+mInterval && g<bottom-mInterval) {
			return DRAG_LEFT;
		} else if (Math.abs(f-right)<=mInterval && g>top+mInterval && g<bottom-mInterval) {
			return DRAG_RIGHT;
		} else if (Math.abs(f-left)<=mInterval && g>top+mInterval && g<bottom-mInterval) {
			return DRAG_LEFT;
		} else if (Math.abs(g-top)<=mInterval && f>left+mInterval && f<right-mInterval) {
			return DRAG_TOP;
		} else if (Math.abs(g-bottom)<=mInterval && f>left+mInterval && f<right-mInterval) {
			return DRAG_BOTTOM;
		} else if (f>left+mInterval && f<right-mInterval
				&& g>top+mInterval && g<bottom-mInterval) {
			return DRAG_WHOLE;
		} else {
			return DRAG_NONE;
		}
	}
	
}

点击下载本文用到的图片基本加工的工程代码 点此查看Android开发笔记的完整目录

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年04月29日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 位图管理Bitmap
  • 图片读写
  • 图片加工
    • 图片压缩
      • 调整大小
        • 图片裁剪
        相关产品与服务
        图片处理
        图片处理(Image Processing,IP)是由腾讯云数据万象提供的丰富的图片处理服务,广泛应用于腾讯内部各产品。支持对腾讯云对象存储 COS 或第三方源的图片进行处理,提供基础处理能力(图片裁剪、转格式、缩放、打水印等)、图片瘦身能力(Guetzli 压缩、AVIF 转码压缩)、盲水印版权保护能力,同时支持先进的图像 AI 功能(图像增强、图像标签、图像评分、图像修复、商品抠图等),满足多种业务场景下的图片处理需求。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档