精华内容
下载资源
问答
  • 比如Android对图片的支持就非常开放,换言之就是非常依赖一个成熟的工具库(比如Glide),(相比web里标签就安全易用很多)。包括本文将实现的工具在内,笔者目前也收集了整整2个成熟好用的图片相关工具类,...

    笔者从零开始开发Android,而且是跳过java直接使用kotlin开发,这其中的好处是可以避开java这门传统语言诸多的潜规则,难处是相比资深Android开发者少了许多可以现用的工具库。比如Android对图片的支持就非常开放,换言之就是非常依赖一个成熟的工具库(比如Glide),(相比web里标签就安全易用很多)。

    包括本文将实现的工具在内,笔者目前也收集了整整2个成熟好用的图片相关工具类,一个就是Glide,另一个是Subsampling Scale Image View,并且膨胀地以为不再需要其他任何图片相关的工具库了。这个想法差点被动摇的一次就是现在需要加一个图片裁剪功能了,一想到网上现有的那些酷炫的工具库,就担心起要不要用和用哪个的问题,但是当笔者看了这篇文章——How We Created uCrop,就对引入第三方图片裁剪库更加抵触了,思来想去自己内心的想法都是:

    为了【传一张图片然后确定参数挖出一张新图片来】这样一个需求而依赖【别人开发的自己无法控制或灵活定制的】库是不值得的。

    本文将纯依赖SubsamplingScaleImageView来实现一个支持缩放和旋转的图片裁剪组件,SubsamplingScaleImageView帮助我完成了其中图片缩放、旋转、拖拽的底层触摸交互以及图片的内存管理工作,这让我可以专心的利用这些动作来确定一件事:我需要裁剪出一张图片如何旋转/缩放后的哪个部分。然后就可以根据这些信息从原图创建出需要的裁剪图。

    依靠SubsamplingScaleImageView已经具备的能力,这个图片裁剪组件的kotlin代码目前包含非关键代码在内也只有300行出头。

    实现过程总览

    纵观整个图片裁剪工具,其实现分这么几步:

    自定义View的基本结构和布局

    拖拽位置边界的保护

    旋转能力和执行裁剪

    自定义View

    首先给这个图片裁剪工具起个难听的名字叫做YmageCutterView,其布局基于ConstraintLayout中间放一个指定宽高的矩形作为裁剪框,四周是四个半透明矩形作为遮罩,底下是原图。设想就是缩放和拖拽原图,再以中间裁剪框作为边界裁剪出目标图。像这样:

    android:layout_height="match_parent"

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    android:background="#333333">

    android:id="@+id/ymage_cutter_origin"

    android:layout_width="match_parent"

    android:layout_height="match_parent" />

    android:layout_width="0dp"

    android:layout_height="0dp"

    android:background="#99000000"

    app:layout_constraintTop_toTopOf="parent"

    app:layout_constraintBottom_toBottomOf="parent"

    app:layout_constraintStart_toStartOf="parent"

    app:layout_constraintEnd_toStartOf="@id/ymage_cutter_frame" />

    android:id="@+id/ymage_cutter_mask_right"

    android:layout_width="0dp"

    android:layout_height="0dp"

    android:background="#99000000"

    app:layout_constraintTop_toTopOf="parent"

    app:layout_constraintBottom_toBottomOf="parent"

    app:layout_constraintStart_toEndOf="@id/ymage_cutter_frame"

    app:layout_constraintEnd_toEndOf="parent"/>

    android:id="@+id/ymage_cutter_mask_top"

    android:layout_width="0dp"

    android:layout_height="0dp"

    android:background="#99000000"

    app:layout_constraintTop_toTopOf="parent"

    app:layout_constraintBottom_toTopOf="@id/ymage_cutter_frame"

    app:layout_constraintStart_toEndOf="@id/ymage_cutter_mask_left"

    app:layout_constraintEnd_toStartOf="@id/ymage_cutter_mask_right"/>

    android:id="@+id/ymage_cutter_mask_bottom"

    android:layout_width="0dp"

    android:layout_height="0dp"

    android:background="#99000000"

    app:layout_constraintBottom_toBottomOf="parent"

    app:layout_constraintTop_toBottomOf="@id/ymage_cutter_frame"

    app:layout_constraintStart_toEndOf="@id/ymage_cutter_mask_left"

    app:layout_constraintEnd_toStartOf="@id/ymage_cutter_mask_right"/>

    android:id="@+id/ymage_cutter_frame"

    android:layout_width="0dp"

    android:layout_height="0dp"

    app:layout_constraintTop_toTopOf="parent"

    app:layout_constraintBottom_toBottomOf="parent"

    app:layout_constraintStart_toEndOf="@id/ymage_cutter_mask_left"

    app:layout_constraintEnd_toStartOf="@id/ymage_cutter_mask_right"

    app:layout_constraintWidth_percent="0.7"

    app:layout_constraintWidth_max="300dp"

    app:layout_constraintDimensionRatio="1:1"/>

    然后为了使裁剪尺寸支持自定义,需要设置一个自定义参数叫ratio,用来传入需要裁剪图片的宽高比,比如支持这么使用:

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    app:ratio="4:3"/>

    上面这样中间的裁剪框就是个4:3的矩形。至于用户通过拖拽实时更改这个裁剪框尺寸这个需求,本文还未去实现,讲真其使用场景也不多,后续再考虑补足。由于使用的是ConstraintLayout,只需要将传入的ratio属性设置给中间裁剪框的layoutParams.dimensionRatio就完成裁剪图片宽高比的更新了。

    拖拽位置边界的保护

    SubsamplingScaleImageView本身已经实现了较完整的触摸交互,包括拖拽和缩放,在它的基础上我们先加一个边界保护:

    originIV.setOnTouchListener { _, event ->

    when (event.action) {

    MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {

    inDrag = false

    inCheck = false

    lazyCheck?.cancel()

    lazyCheck = null

    lazyCheck = Timer().schedule(100) {

    if (!inDrag && !inCheck) {

    inCheck = true

    resolvePositionCheck()

    }

    }

    }

    MotionEvent.ACTION_DOWN -> {

    inDrag = true

    }

    }

    return@setOnTouchListener false

    }

    以上代码做了这么几件事情:

    手指按下时指示正在拖拽

    手指抬起后100毫秒内若未再次按下,则执行边界检查

    边界检查实现如下:

    private fun resolvePositionCheck() {

    val center = originIV.center ?: return

    val x: Float

    val y: Float

    if (originIV.orientation == rotation0 || originIV.orientation == rotation180) {

    val left = frameV.width/2/originIV.scale

    val top = frameV.height/2/originIV.scale

    val right = left + (originIV.sWidth*originIV.scale - frameV.width)/originIV.scale

    val bottom = top + (originIV.sHeight*originIV.scale - frameV.height)/originIV.scale

    limitRect.set(left, top, right, bottom)

    x = if (center.x < limitRect.left) {

    if (originIV.sWidth*originIV.scale < frameV.width) {

    if (center.x < limitRect.right) {

    limitRect.right

    } else {

    center.x

    }

    } else {

    limitRect.left

    }

    } else if (center.x > limitRect.right) {

    if (originIV.sWidth*originIV.scale < frameV.width) {

    limitRect.left

    } else {

    limitRect.right

    }

    } else {

    center.x

    }

    y = if (center.y < limitRect.top) {

    if (originIV.sHeight*originIV.scale < frameV.height) {

    if (center.y < limitRect.bottom) {

    limitRect.bottom

    } else {

    center.y

    }

    } else {

    limitRect.top

    }

    } else if (center.y > limitRect.bottom) {

    if (originIV.sHeight*originIV.scale < frameV.height) {

    limitRect.top

    } else {

    limitRect.bottom

    }

    } else {

    center.y

    }

    } else {

    val left = frameV.width/2/originIV.scale

    val top = frameV.height/2/originIV.scale

    val right = left + (originIV.sHeight*originIV.scale - frameV.width)/originIV.scale

    val bottom = top + (originIV.sWidth*originIV.scale - frameV.height)/originIV.scale

    limitRect.set(left, top, right, bottom)

    x = if (center.x < limitRect.left) {

    if (originIV.sHeight*originIV.scale < frameV.width) {

    if (center.x < limitRect.right) {

    limitRect.right

    } else {

    center.x

    }

    } else {

    limitRect.left

    }

    } else if (center.x > limitRect.right) {

    if (originIV.sHeight*originIV.scale < frameV.width) {

    limitRect.left

    } else {

    limitRect.right

    }

    } else {

    center.x

    }

    y = if (center.y < limitRect.top) {

    if (originIV.sWidth*originIV.scale < frameV.height) {

    if (center.y < limitRect.bottom) {

    limitRect.bottom

    } else {

    center.y

    }

    } else {

    limitRect.top

    }

    } else if (center.y > limitRect.bottom) {

    if (originIV.sWidth*originIV.scale < frameV.height) {

    limitRect.top

    } else {

    limitRect.bottom

    }

    } else {

    center.y

    }

    }

    if (x != center.x || y != center.y) {

    GlobalScope.launch(Dispatchers.Main) {

    originIV.animateCenter(PointF(x, y))

    ?.withDuration(100)

    ?.withEasing(SubsamplingScaleImageView.EASE_OUT_QUAD)

    ?.withInterruptible(false)

    ?.start()

    }

    }

    }

    算是这个裁剪组件里最长的一个方法了,比较不美观的套了很多条件检查值得去优化,不过做的事情描述起来很简单,就是如果原图没有完全包含在裁剪框中则按最近路径移动进来。比如安卓微信里的头像裁剪,图片就可以任意移出裁剪框,笔者觉得这样不妥。

    旋转能力和执行裁剪

    图片的旋转只需设置SubsamplingScaleImageView的orientation即可,麻烦的是不同旋转角度下原图的宽高要区分处理,也就是说,0度和180度下原图的宽就是宽,高就是高,但90度和270度下原图的宽是高而高是宽。这在上文边界保护和下文执行裁剪时都要加以区分处理。

    执行裁剪作为一个方法提供给外界调用,实现如下:

    fun shutter(): Bitmap? {

    if (resultBitmap?.isRecycled == false) {

    resultBitmap?.recycle()

    }

    val center = originIV.center ?: return null

    val cut = Rect(

    (center.x - frameV.width/2/originIV.scale).toInt(),

    (center.y - frameV.height/2/originIV.scale).toInt(),

    (center.x + frameV.width/2/originIV.scale).toInt(),

    (center.y + frameV.height/2/originIV.scale).toInt()

    )

    val bitmap = BitmapFactory.decodeFile(src)

    val matrix = Matrix()

    matrix.postRotate(originIV.orientation.toFloat())

    val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)

    resultBitmap = Bitmap.createBitmap(rotatedBitmap, Math.max(cut.left, 0), Math.max(cut.top, 0), Math.min(cut.right-cut.left, rotatedBitmap.width), Math.min(cut.bottom-cut.top, rotatedBitmap.height), null, true)

    if (bitmap != resultBitmap && !bitmap.isRecycled) {

    bitmap.recycle()

    }

    if (rotatedBitmap != resultBitmap && !rotatedBitmap.isRecycled) {

    rotatedBitmap.recycle()

    }

    return resultBitmap

    }

    此方法做的事情就是,先将原图根据当前的旋转角度旋转得到源bitmap,再根据当前原图的缩放和位置确定裁剪图片的起点坐标(x,y)以及宽高,调用createBitmap挖出裁剪图,最后返回裁剪后的目标bitmap。

    总结

    至此这个图片裁剪组件已经完成,说白了是以自定义View的形式对SubsamplingScaleImageView进行扩展和二次封装,使用起来需要把这个View放到自己的Activity中,目前支持传入ratio属性设置裁剪图的宽高比,并提供了这么几个方法供调用:

    reset() 重设图片的旋转和缩放

    rotate(degree: Int) 执行旋转,角度必须为 0, 90, 180, 270的其中一个

    shutter(): Bitmap? 按快门方法,执行裁剪并返回bitmap

    最后再次膜拜SubsamplingScaleImageView并附上本文项目Github地址。

    展开全文
  • java 图片裁剪

    2021-03-08 08:46:04
    } /** * * 对图片裁剪,并把裁剪完的新图片保存 。 * */ public void cut() throws IOException { FileInputStream is = null; ImageInputStream iis = null; try { // 读取图片文件 is = new FileInputStream...

    [java]代码库package com.wodexiangce;

    import java.awt.Rectangle;

    import java.awt.image.BufferedImage;

    import java.io.File;

    import java.io.FileInputStream;

    import java.io.IOException;

    import java.util.Iterator;

    import javax.imageio.ImageIO;

    import javax.imageio.ImageReadParam;

    import javax.imageio.ImageReader;

    import javax.imageio.stream.ImageInputStream;

    /**

    *

    * 2012-10-25

    *

    */

    public class OperateImage {

    // ===源图片路径名称如:c:\1.jpg

    private String srcpath;

    // ===剪切图片存放路径名称。如:c:\2.jpg

    private String subpath;

    // ===剪切点x坐标

    private int x;

    private int y;

    // ===剪切点宽度

    private int width;

    private int height;

    public OperateImage() {

    }

    public OperateImage(int x, int y, int width, int height) {

    this.x = x;

    this.y = y;

    this.width = width;

    this.height = height;

    }

    /**

    *

    * 对图片裁剪,并把裁剪完的新图片保存 。

    *

    */

    public void cut() throws IOException {

    FileInputStream is = null;

    ImageInputStream iis = null;

    try {

    // 读取图片文件

    is = new FileInputStream(srcpath);

    /**

    *

    * 返回包含所有当前已注册 ImageReader 的 Iterator,这些 ImageReader

    *

    * 声称能够解码指定格式。 参数:formatName - 包含非正式格式名称 .

    *

    * (例如 "jpeg" 或 "tiff")等 。

    *

    */

    Iterator it = ImageIO

    .getImageReadersByFormatName(" jpg ");

    ImageReader reader = it.next();

    // 获取图片流

    iis = ImageIO.createImageInputStream(is);

    /**

    *

    *

    * iis:读取源。true:只向前搜索

    *

    * .将它标记为 ‘只向前搜索’。

    *

    * 此设置意味着包含在输入源中的图像将只按顺序读取,可能允许 reader

    *

    * 避免缓存包含与以前已经读取的图像关联的数据的那些输入部分。

    *

    */

    reader.setInput(iis, true);

    /**

    *

    *

    * 描述如何对流进行解码的类

    *

    * .用于指定如何在输入时从 Java Image I/O

    *

    * 框架的上下文中的流转换一幅图像或一组图像。用于特定图像格式的插件

    *

    * 将从其 ImageReader 实现的 getDefaultReadParam 方法中返回

    *

    * ImageReadParam 的实例。

    *

    */

    ImageReadParam param = reader.getDefaultReadParam();

    /**

    *

    * 图片裁剪区域。Rectangle 指定了坐标空间中的一个区域,通过 Rectangle 对象

    *

    * 的左上顶点的坐标(x,y)、宽度和高度可以定义这个区域。

    *

    */

    Rectangle rect = new Rectangle(x, y, width, height);

    // 提供一个 BufferedImage,将其用作解码像素数据的目标。

    param.setSourceRegion(rect);

    /**

    *

    * 使用所提供的 ImageReadParam 读取通过索引 imageIndex 指定的对象,并将

    *

    * 它作为一个完整的 BufferedImage 返回。

    *

    */

    BufferedImage bi = reader.read(0, param);

    // 保存新图片

    ImageIO.write(bi, " jpg ", new File(subpath));

    } finally {

    if (is != null)

    is.close();

    if (iis != null)

    iis.close();

    }

    }

    public int getHeight() {

    return height;

    }

    public void setHeight(int height) {

    this.height = height;

    }

    public String getSrcpath() {

    return srcpath;

    }

    public void setSrcpath(String srcpath) {

    this.srcpath = srcpath;

    }

    public String getSubpath() {

    return subpath;

    }

    public void setSubpath(String subpath) {

    this.subpath = subpath;

    }

    public int getWidth() {

    return width;

    }

    public void setWidth(int width) {

    this.width = width;

    }

    public int getX() {

    return x;

    }

    public void setX(int x) {

    this.x = x;

    }

    public int getY() {

    return y;

    }

    public void setY(int y) {

    this.y = y;

    }

    public static void main(String[] args) throws Exception {

    String name = " d:\2005121210161588950.jpg ";

    OperateImage o = new OperateImage(100, 100, 100, 100);

    o.setSrcpath(name);

    o.setSubpath(" D:\2.jpg ");

    o.cut();

    }

    }

    //源代码片段来自云代码http://yuncode.net

    694748ed64b9390909c0d88230893790.png

    展开全文
  • 做照片相框步骤就是跟美图秀秀一样,先截图,然后添加相框在之前Bitmap.createBitmap那个文里,说过使用jpg图片会挡住底层图片,用画这种方法合成图片,没有问题。public class MainActivity extends Activity ...

    做照片相框

    步骤就是跟美图秀秀一样,先截图,然后添加相框

    在之前Bitmap.createBitmap那个文里,说过使用jpg图片会挡住底层图片,用画这种方法合成图片,没有问题。

    public class MainActivity extends Activity implements OnClickListener{

    private static final String TAG = "blueweb";

    private LinearLayout layout_btnGroup;

    private int paintY;// layout_btnGroup高度

    private Button selectImageBtn;

    private Button cutImageBtn;

    private Button btn_ouerlay;

    private ImageView imageView;

    private Bitmap cutBmp;// 裁剪得到的Bitmap

    private Bitmap chooseBmp;// 选择相框的Bitmap

    private Canvas canvas;

    private Paint paint;

    private boolean onePicked = false;

    private boolean twoPicked = false;

    private static final int  IMAGE_SELECT = 1;

    private static final int  IMAGE_CUT = 2;

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    layout_btnGroup = (LinearLayout) findViewById(R.id.layout_btnGroup);

    selectImageBtn = (Button) findViewById(R.id.btn_chooseimg);

    cutImageBtn = (Button) findViewById(R.id.btn_cutimg);

    btn_ouerlay = (Button) findViewById(R.id.btn_addoverlay);

    imageView = (ImageView) findViewById(R.id.image);

    selectImageBtn.setOnClickListener(this);

    cutImageBtn.setOnClickListener(this);

    btn_ouerlay.setOnClickListener(this);

    paintY = layout_btnGroup.getHeight();

    }

    public void onClick(View v) {

    Intent intent;

    switch(v.getId()){

    case R.id.btn_chooseimg:

    intent = new Intent(Intent.ACTION_PICK,

    android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);

    startActivityForResult(intent, IMAGE_SELECT);

    break;

    case R.id.btn_cutimg:

    intent = getImageClipIntent();

    startActivityForResult(intent, IMAGE_CUT);

    break;

    case R.id.btn_addoverlay:

    Toast.makeText(this, "点击了Btn", Toast.LENGTH_SHORT).show();

    chooseBmp = BitmapFactory.decodeResource(getResources(), R.drawable.img_overlay);

    if (chooseBmp != null){

    twoPicked = true;

    if (onePicked == true && twoPicked == true){

    Bitmap drawingBitmap = Bitmap.createBitmap(cutBmp.getWidth(), cutBmp.getHeight(), cutBmp.getConfig());

    canvas = new Canvas(drawingBitmap);

    paint = new Paint();

    canvas.drawBitmap(cutBmp, 0, paintY, paint);

    paint.setXfermode(new PorterDuffXfermode(android.graphics.PorterDuff.Mode.MULTIPLY));

    canvas.drawBitmap(chooseBmp, 0, paintY, paint);

    Log.d(TAG, paintY + "");

    imageView.setImageBitmap(drawingBitmap);

    }

    }else {

    Toast.makeText(this, "添加相框失败", Toast.LENGTH_SHORT).show();

    }

    break;

    }

    }

    /**

    * 获取剪切后的图片

    */

    public static Intent getImageClipIntent() {

    Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);

    intent.setType("image/*");

    intent.putExtra("crop", "true");

    intent.putExtra("aspectX", 1);//裁剪框比例

    intent.putExtra("aspectY", 1);

    intent.putExtra("outputX", 200);//输出图片大小

    intent.putExtra("outputY", 300);

    intent.putExtra("return-data", true);

    return intent;

    }

    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {

    if(resultCode == RESULT_OK){

    if(requestCode == IMAGE_SELECT){

    Uri imageFileUri = intent.getData();

    int dw=getWindowManager().getDefaultDisplay().getWidth();

    int dh=getWindowManager().getDefaultDisplay().getHeight()/2;

    //已屏幕宽 和一般的高作为图片显示的最大尺寸

    try{

    BitmapFactory.Options factory = new BitmapFactory.Options();

    factory.inJustDecodeBounds = true; //当为true时  允许查询图片不为 图片像素分配内存

    cutBmp = BitmapFactory.decodeStream(getContentResolver()

    .openInputStream(imageFileUri),null,factory);

    int hRatio = (int)Math.ceil(factory.outHeight/(float)dh); //图片是高度的几倍

    int wRatio = (int)Math.ceil(factory.outWidth/(float)dw); //图片是宽度的几倍

    System.out.println("hRatio:"+hRatio+"  wRatio:"+wRatio);

    //缩小到  1/ratio的尺寸和 1/ratio^2的像素

    if(hRatio>1 || wRatio>1){

    if(hRatio > wRatio){

    factory.inSampleSize = hRatio;

    }

    else

    factory.inSampleSize = wRatio;

    }

    factory.inJustDecodeBounds = false;

    cutBmp = BitmapFactory.decodeStream(getContentResolver()

    .openInputStream(imageFileUri),null,factory);

    if (cutBmp != null){

    onePicked = true;

    imageView.setImageBitmap(cutBmp);

    }else {

    Toast.makeText(this, "选择图片失败", Toast.LENGTH_SHORT).show();

    }

    }catch(Exception ex){

    }

    }

    else if(requestCode == IMAGE_CUT){

    cutBmp = intent.getParcelableExtra("data");

    if (cutBmp != null){

    onePicked = true;

    imageView.setImageBitmap(cutBmp);

    }else {

    Toast.makeText(this, "获取裁剪图片失败", Toast.LENGTH_SHORT).show();

    }

    }

    }

    }

    }

    布局文件

    xmlns:tools="http://schemas.android.com/tools"

    android:orientation="vertical"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    tools:context=".MainActivity" >

    android:id="@+id/layout_btnGroup"

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:orientation="vertical" >

    android:id="@+id/btn_chooseimg"

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:text="选择图片" />

    android:id="@+id/btn_cutimg"

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:text="选择图片裁剪" />

    android:id="@+id/btn_addoverlay"

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:text="添加相框" />

    android:layout_weight="1"

    android:id="@+id/image"

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:src="@drawable/ic_launcher" />

    先点击选择图片裁剪,然后点击添加相框。效果如下,只是做了基本的原理,以后有时间会改一下代码再更新

    1359535288_2544.png

    1359535301_2169.png

    1359535354_6099.png

    展开全文
  • 基于HTML5+JS实现本地图片裁剪并上传功能2019-01-07编程...最近做了一个项目,这个项目中需要实现的一个功能是:用户自定义头像(用户在本地选择一张图片,在本地将图片裁剪成满足系统要求尺寸的大小)。这个功能的...

    基于HTML5+JS实现本地图片裁剪并上传功能

    2019-01-07

    编程之家

    https://www.jb51.cc

    编程之家收集整理的这篇文章主要介绍了基于HTML5+JS实现本地图片裁剪并上传功能,编程之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

    最近做了一个项目,这个项目中需要实现的一个功能是:用户自定义头像(用户在本地选择一张图片,在本地将图片裁剪成满足系统要求尺寸的大小)。这个功能的需求是:头像最初剪切为一个正方形。如果选择的图片小于规定的头像要求尺寸,那么这整张图片都会作为头像。如果大于规定的尺寸,那么用户可以选择要裁剪的区域。用户点击确定按钮,就将裁剪得到的图片数据发送到服务器,在后端将图片数据保存成一个文件。

    要完成上述功能,涉及到的知识有:ajax,canvas和html5中的files接口。我将实现这个功能的代码封装到了4个模块中,分别是ajax.js,preview.js,shear.js和customerImg.js。

    ajax.js:用于发送ajax请求。

    preview.js:用于图片预览

    shear.js:用于裁剪图片

    customer.js:自定义头像。在这个模块中药引入ajax.js,preview.js和shear.js

    我使用webpack进行打包。我还使用了jquery和jquery-ui。

    我从这个项目中抽离出了这个功能。下面是这个功能的详细代码。

    图片

    预览

    40488.html

    2.CSS代码

    有了css和html的运行结果如下:

    3.js代码

    customerImg.js

    自定义头像

    * @constructor

    */

    function CustomerImg() {

    this.isSupport = null;

    this.previewBox = null;

    this.warp = null;

    }

    /**

    * 入口

    * @param warp 操作区域 jquery节点

    */

    CustomerImg.prototype.start = function (warp) {

    var info,me,warpBox;

    me = this;

    this.isSupport = this.__isSupport();

    if(!this.isSupport) {

    info = $('

    你的浏览器不支持自定义头像,可更换高版本的浏览器自定义头像');

    $('body').html(info);

    return this;

    }

    //判断操作区域示范存在

    if(warp && warp.length > 0){

    this.warp = warp;

    }else{

    return this;

    }

    //预览

    preview.start(warp,shear.start.bind(shear,warp));

    this.previewBox = warp.find('#preview');

    //确定

    warp

    .find('#submit')

    .unbind('click')

    .on('click',me.__submit.bind(me));

    };

    /**

    * 提交

    * @private

    */

    CustomerImg.prototype.__submit = function () {

    var cvsMove,data,fd;

    cvsMove = this.previewBox.find('#cvsMove');

    data = cvsMove[0].toDataURL('image/jpg',1);

    fd = {

    'customerImg':data

    };

    ajax.upload(fd);

    };

    /**

    * 判断是否支持自定义头像

    * @returns {boolean}

    * @private

    */

    CustomerImg.prototype.__isSupport = function () {

    var canvas,context;

    canvas= document.createElement('canvas');

    if(typeof FileReader === 'function'&& canvas.getContext && canvas.toDataURL){

    return true;

    }else{

    return false;

    }

    };

    var customerImg = new CustomerImg();

    module.exports = customerImg;

    preview.js

    BoxElem = null;

    this.callback = null;

    this.type = null;

    }

    /**

    * 入口

    * @param BoxElem 操作区域

    * @param callback 预览结束的回调函数

    */

    Preview.prototype.start = function (BoxElem,callback) {

    var chooseFile,me;

    me = this;

    if(! BoxElem || BoxElem.length <= 0) return this;

    this.BoxElem = BoxElem;

    if(typeof callback === 'function'){

    this.callback = callback;

    }

    if(this.__isSupport()){

    chooseFile = BoxElem.find('input[type="file"]');

    chooseFile

    .on('change',me.fileChange.bind(me))

    }

    };

    /**

    * 选择图片的事件处理程序

    * @param event

    */

    Preview.prototype.fileChange = function (event) {

    var target,reader,file,type;

    target = event.target;

    me = this;

    file = target.files[0];

    type = file.type;

    this.type = type;

    if(type !== 'image/png' && type !== 'image/jpg' && type !== 'image/jpeg'){

    alert('文件格式不正确');

    return this;

    }

    reader = new FileReader();

    if(file){

    reader.readAsDataURL(file);

    }

    reader.onload = function () {

    me.show(reader);

    }

    };

    /**

    * 显示从本地选择的图片

    * @param reader fileReader对象

    */

    Preview.prototype.show = function (reader) {

    var preView,img,me;

    preView = this.BoxElem.find('#preview');

    img = preView.find('#preImg');

    me = this;

    if(img.length <= 0){

    preView.append($('

    40488.html图片加载完成后再执行回调

    img.on('load',function () {

    if(me.callback){

    me.callback(me.type);

    }

    });

    img.attr('src',reader.result);

    };

    /**

    * 是否支持预览

    * @returns {boolean}

    * @private

    */

    Preview.prototype.__isSupport = function () {

    return typeof FileReader === 'function';

    };

    var preview = new Preview();

    module.exports = preview;

    shear.js

    Box = null;

    this.cvsMove = null;

    this.maxW = 200;

    this.maxH = 200;

    this.thum = null;

    this.fileType = 'image/jpeg';

    }

    /**

    * 入口

    * @param previewBox 预览元素的父元素

    * @param fileType 裁剪的图片的类型 如:'image/jpg'

    * @returns {Shear}

    */

    Shear.prototype.start = function (previewBox,fileType) {

    if(!arguments.length) return this;

    var me = this;

    this.previewBox = previewBox;

    if(fileType){

    this.fileType = fileType;

    }

    this.thum = this.previewBox.find('#thum');

    this.cvsMove = this.previewBox.find('#cvsMove');

    this.showCanvas();

    return this;

    };

    /**

    显示出canvas

    /

    Shear.prototype.showCanvas = function () {

    var preImg,h,w,cvsH,cvsW,rateH,rateW,naturalH,naturalW,preview;

    me = this;

    preImg = this.previewBox.find('#preImg');

    preview = this.previewBox.find('#preview');

    naturalH = preImg[0].naturalHeight;

    naturalW = preImg[0].naturalWidth;

    //将canvas显示出来

    this.cvsMove.show();

    //将canvas置于(0,0)

    this.cvsMove

    .css({

    "left":'0','top':'0'

    });

    h = preImg.height();

    w = preImg.width();

    //规定裁剪出的图片尺寸为200px200px

    //要保证裁剪的图片不变形

    if(h < this.maxH || w < this.maxW){

    this.cvsMove[0].width = cvsW = Math.min(h,w);

    this.cvsMove[0].height = cvsH = Math.min(h,w);

    }else{

    this.cvsMove[0].width= cvsW = this.maxW;

    this.cvsMove[0].height= cvsH = this.maxH;

    }

    rateH = h/naturalH;

    rateW = w/naturalW;

    this.__drawImg(preImg,cvsW/rateW,cvsH/rateH,cvsH);

    //使用jquery-ui中的功能使canvas可以移动

    this.cvsMove.draggable(

    {

    containment: "parent",drag:function (event,ui) {

    var left,top;

    left = ui.position.left;

    top = ui.position.top;

    //canvas每次移动都有从新绘制图案

    me.__drawImg(preImg,left/rateW,top/rateH,cvsH);

    }

    }

    )

    };

    /**

    在canvas上显示图片

    @param myImg 要显示的图片节点

    @param sx 图片的起点在原图片上的x坐标

    @param sy 图片的起点在原图上的y坐标

    @param sW 在原图上的宽度

    @param sH 在原图上的高度

    @param dx 起点在canvas上的x坐标

    @param dy 起点在canvas上的y坐标

    @param dW 在canvas上的宽度

    @param dH 在canvas上的高度

    @private

    */

    Shear.prototype.__drawImg = function (myImg,sx,sy,sW,sH,dx,dy,dW,dH) {

    var cxt,thum,me;

    me = this;

    cxt = this.cvsMove[0].getContext('2d');

    cxt.drawImage(myImg[0],dH);

    thum = this.thum;

    //将canvas上的图案显示到右侧

    thum

    .attr('src',this.cvsMove[0].toDataURL(me.fileType,1))

    .width(this.maxW)

    .height(this.maxH)

    };

    var shear = new Shear();

    module.exports = shear;

    ajax.js

    }

    /**

    上传图片数据

    */

    Ajax.prototype.upload = function (data) {

    $.ajax({

    type:'POST',data:data,dataType:'json',url:'/test/PHP/upload.PHP',success:function (result) {

    if(result.status){

    location.reload();

    }else{

    alert(result.msg);

    }

    }

    });

    };

    var ajax = new Ajax();

    module.exports = ajax;

    最后在另一个文件中,调用customerImg对象的start方法

    webpack的配置文件如下:

    PHP;">

    if(!empty($_POST) && isset($_POST['customerImg'])){

    $img = $_POST['customerImg'];

    $imgdata = explode(',',$img);

    $uniName = md5 ( uniqid ( microtime ( true ),true ) );

    $a = file_put_contents('./../uploads/'.$uniName.'.jpg',base64_decode($imgdata[1]));

    }

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程之家。

    总结

    如果觉得编程之家网站内容还不错,欢迎将编程之家网站推荐给程序员好友。

    本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。

    小编个人微信号 jb51ccc

    喜欢与人分享编程技术与工作经验,欢迎加入编程之家官方交流群!

    展开全文
  • Android图片裁剪之自由裁剪我的博客http://blog.csdn.net/dawn_moon客户的需求都是很怪的。我有时候在给客户做项目的时候就想骂客户是sb。但是请你相信我,等你有需求,自己变成客户的时候,给你做项目的哥哥肯定也...
  • 这应该算是一个造轮子的实践,JS的图片开源裁剪器有很多,像使用JQuery库编写的cropper插件很多,在github上边的star数量也不少,现流行的前端框架也肯定有对应的图片裁剪器,都是可以选择的成熟的技术方案。...
  • 实现:tkinter画布上显示图片,按下鼠标左键并且移动,实现截图# -*- encoding=utf-8 -*-import osimport tkinter as tkfrom PIL import Imagefrom PIL import ImageTkleft_mouse_down_x = 0left_mouse_down_y = 0...
  • css 图片裁剪显示

    2021-08-04 08:37:27
    &period;...period;UnauthorizedAccessException的解决方法假设VS代码对应路径为E:\Projects\Web1,在VS用“发布Web”的方式发布后的路径为E:\Site\Web1.在IIS新建2个站点,站点A指向E:\Projects\Web1,站点B指向 ......
  • Java图片裁剪

    2021-02-12 09:47:21
    public static void cut(String srcpath,String subpath,int x,int y,int width,int height) throwsIOException{FileInputStream is= null;...try{//读取图片文件is = newFileInputStream(srcpath)...
  • Android 图片裁剪 (附源码)

    千次阅读 2021-03-27 11:05:27
    Android 图片裁剪前言正文一、创建并配置项目二、权限申请三、获取图片Uri四、图片裁剪尾声 运行效果图 前言   图片裁剪是对图片进行区域选定,然后裁剪选定的区域,形成一个图片,然后再对这个图片进行压缩,...
  • 是这种,客户须要做一个图片上传的功能,这个图片须要裁剪。一般而言,这东西用系统自带的裁剪就搞定了。但是客户不,他要能够自由裁剪,就是长宽比不固定,想裁成什么比例就裁成什么比例,我一听,蛋都碎了。没有...
  • 图片指定大小然后由php相关函数来...方法一 代码如下 复制代码 图片裁剪缩放函数,支持方位裁剪和自定义坐标裁剪/*** 图片裁剪函数,支持指定定点裁剪和方位裁剪两种裁剪模式* @param $src_file 原图片路径* @param ...
  • 最近工作中需要封装一个图片裁剪组件,也是封装了两天时间才完成,特此记录便于日后查阅。 先看一下最终效果,图示如下: 裁剪之后的图片可以预览,下载,删除,然后我们来看看代码: 1、UploadCutImage....
  • 这就需要在用户上传的时候对图片进行合适大小裁剪。 大概思路:用户上传图片时 判断图片尺寸大小,宽高不能小于1200px240;小于这个尺寸将无法完美裁剪。 将图片转化为base64格式,即时回显图片和不浪费服务器...
  • 前言我们都知道css将一张图片变成圆角图片很容易,通过border-radius就可以实现,但是canvas上面...image.png首先我们先绘制一张图片image.png然后咱们利用setClipPath对图片进行裁剪image.png代码#main {width: 400...
  • 图片裁剪&矩形复原图片裁剪&矩形复原操作流程1.有张照片2.看看照片3.选择角点4.最终结果代码分析懒人一键复制代码 图片裁剪&矩形复原 这篇博文的目的是从图片中提取一定的矩形区域作为新的图片 简单来说...
  • 主要实现文件夹下面大量相似图片裁剪以及拼接,并且最终生成拼接后的PDF。
  • reader.onload = () => { var img = new Image() img.src = reader.result img.onload = () => { // 1、先根据图片裁剪的尺寸 if (this.options.autoCropWidth > this.options.autoCropHeight) { // 2、再根据上传...
  • 本文实例讲述了java实现的图片裁剪功能。分享给大家供大家参考,具体如下:PicCut.java:package Tsets;import java.awt.Rectangle;import java.awt.image.BufferedImage;import java.io.File;import java.io....
  • 文章目录前言效果图源代码DashLineWidget.dartCornerImageWidget.dartCorpImageWidget.dartCSDN C币赞助下载(5C币)后记...另外需要自备4张触点图片。 DashLineWidget.dart import 'dart:async'; import 'dart:math'
  • HTML5 实现本地图片裁剪 文章目录HTML5 实现本地图片裁剪1.知识点1.HTML 结构与 CSS 样式2.初始化3 实现 handleFiles,获取文件,读取文件并生成 url4.实现 paintImage 方法5 实现 cutImage 方法6 编写 drag 方法7 ...
  • VUE图片裁剪组件,基于VueCropper 搬砖的同志麻烦点个赞支持下 第一步安装vue-cropper插件 npm install vue-cropper 第二步创建组件,把下面的代码复制过去,注意修改下上传图片的接口 <template> <a-card :...
  • 我们在开发中经常需要使用截屏功能或者把某个view生成一张图片的功能,还有可能需要拼接在一起组成一张大图,另外有可能给一张大图添加水印图等。屏幕截图屏幕截图和view截成图一样,只是把对应的view换成window就行...
  • Java实现图片裁剪预览功能在项目中,我们需要做些类似头像上传,图片裁剪的功能,ok看下面文章!需要插件:jQueryJcrop后端代码:packageorg.csg.upload;importjava.awt.Rectangle;importjava.awt.p_w_picpath....
  • Java中实现图片裁剪

    2021-03-14 22:04:43
    Java如何截取图片Author:zhyiwwwDate:2007-1-30转载请注明出处www.BlogJava.net/zhyiwww(copyright by @ zhangyi)下面是我的一段代码,实现如何截取图片的://图片源privatestatic final String SRC_FILE="org//zy//...
  • Android中头像选择,图片上传等功能几乎是每一个APP必备的功能,那么关于怎么使用相机,如何进行照片选择,以及选择后的图片裁剪,这一系列的问题都需要逐一解决。这也是本篇文章的主要内容。一、应用场景微信朋友圈...
  • 个人感觉图片裁剪功能还是很实用的,故写篇文章分享一下。(PS:这个demo本来是移动端的,但了为了方便录制效果,所以我用chrome模拟地跑了一下。还有就是demo是配合Vue实现的,因为我是直接在项目中复制过来改改的,...
  • 首先非常感觉大佬的图片裁剪插件 image-cropper,下面就是大佬的地址 文章地址https://developers.weixin.qq.com/community/develop/article/doc/000aecd2f38df8efb55a0250b5bc13 大佬Demo地址:...
  • Vue中图片裁剪的实现

    千次阅读 2021-01-14 14:17:21
    最近做项目有裁剪本地图片并保存为Base64数据的需求,鼓捣了一会弄出来了,分享一下一个好用的工具 先看一下效果 vue-cropper github地址:https://github.com/xyxiao001/vue-cropper 安装 npm install vue-...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 96,115
精华内容 38,446
关键字:

图片裁剪