Djang图片上传和剪切

Django呐!🤣 图片🖼(含GIF)上传和剪切✂

在这里插入图片描述

效果展示

#静图剪切

在这里插入图片描述

#动图剪切(这一步其实挺慢的,中间等待的部分剪去了)
在这里插入图片描述

Python 实现图片剪切

Python对图片处理的库是非常丰富的,所以有很多库能实现这一目标的方法如Pillow,OpenCV,moviepy等等,而这里给的gif处理方法主要使用的是Pillow,moviepy库的

安装Pillow,moviepy

pip install Pillow
pip install moviepy

对于图片的剪切主要是通过坐标进行判断,需要提供一个矩形坐标,即四个坐标点进行定位,然后对原图进行剪切处理。

那么其他废话不多说,直接上代码。(学别人讲的话,直接上代码确实很爽)

除GIF的图片文件处理

U_x = 144.73429951690818		    # x轴坐标
U_y = 76.13526570048306			# y轴坐标
U_width = 640.0000000000001		# 剪切大小
U_height = 640.0000000000001
u_rotate = 90					# 旋转角度

img = Image.open('input.img')
    crop_im = img.crop((U_x, U_y, U_x + U_width, U_y + U_height)).resize((400, 400),Image.LANCZOS).rotate(u_rotate)
    # 注意这里的Image.LANCZOS,如果你的Pillow是10.0直接CV那么啥事情,但是其他的版本可能是Image.ANTIALIAS如9.5

备注:LANCZOS实际上和ANTIALIAS指向的是同一种图像插值模式算法

GIF图片文件处理

#用Pillow处理

# -*- coding: utf-8 -*-
# @Time    : 2023/8/3 11:25
# @Author  : BXZDYG
# @Software: PyCharm
# @File    : Pillow_crop_gif.py
import time

from PIL import Image, ImageSequence

if __name__ == '__main__':
    first_time = time.time()    	  #时间戳检验剪切速度
    start=time.perf_counter()         #计算机时间检验剪切速度
    U_x = 144.73429951690818		    # x轴坐标
    U_y = 76.13526570048306			# y轴坐标
    U_width = 640.0000000000001		# 剪切大小
    U_height = 640.0000000000001
    u_rotate = 90					# 旋转角度
    frames = []

    with Image.open('input.gif') as im:
        idx = 0
        for frame in ImageSequence.Iterator(im):
            frame = frame.crop((U_x, U_y, U_x + U_width, U_y + U_height)).resize((400, 400)).rotate(u_rotate)
            frame.info['duration'] = im.info['duration']
            frames.append(frame)
            idx += 1
    frames[0].save('out.gif',
                   save_all=True, append_images=frames[1:], loop=0, duration=im.info['duration'], quality=80)
    print('时间戳说——总共用时:',time.time()-first_time,'秒')   # 总共用时: 4-5 秒
    print('计算机说——总共用时:',time.perf_counter()-start,'秒')   # 总共用时: 4-5 秒

#用moviepy进行图片剪切

备注:如果你要求对分辨率的在处理的话,那么需要Pillow的版本为9.5,10.0和其底层的resize中设置的图像插值模式冲突,是Image.ANTIALIAS,手动打上去还改不了,而且速度极慢,是用Pillow和结合Pillow库的两倍。而且在movie官方文档也不建议在大型框架和web上使用moviepy.editor子模块
在这里插入图片描述

moviepy-cn 文档

# @Time    : 2023/8/3 11:26
# @Author  : BXZDYG
# @Software: PyCharm
from moviepy.editor import *
import time

if __name__ == '__main__':
    first_time = time.time()  # 时间戳检验剪切速度
    start = time.perf_counter()  # CPU时间检验剪切速度
    x = 144.73429951690818
    y = 76.13526570048306
    width = 640.0000000000001
    height = 640.0000000000001
    t_rotate = 90
    gif = VideoFileClip('head_cap.gif')
    # 获取帧数
    nframes = gif.reader.nframes

    # 设置每帧duration
    durations = [0.1] * nframes

    images = [gif.get_frame(t) for t in range(nframes)]

    clip = ImageSequenceClip(images, durations=durations)

    rotated = clip.crop(x1=x, y1=y, x2=x + width, y2=y + height).resize((400, 400)).rotate(t_rotate)
    # resized = rotated.resize((400, 400)) # 调整分辨率
    # resized.write_gif('23.gif', fps=20)
    rotated.write_gif('23.gif', fps=20)

    print('时间戳说——总共用时:', time.time() - first_time, '秒')  # 总共用时: 10+ 秒
    print('CPU说——总共用时:', time.perf_counter() - start, '秒')  # 总共用时: 10+ 秒

#用Pillow对每一帧图片进行剪切,moviepy生成gif文件

# -*- coding: utf-8 -*-
# @Time    : 2023/8/3 11:26
# @Author  : BXZDYG
# @Software: PyCharm
# @File	   : Movie_Pillow_crop_gif.py
import time

import numpy as np
from PIL import Image
from moviepy.editor import VideoFileClip


def pillow_movie_crop(img):
    '''
    :param img:
    :return: 存在缺点帧数丢失,图片变大
    '''
    im = Image.fromarray(img)
    cropped = im.crop((x, y, x+width, y+height)).resize((400,400)).rotate(t_rotate)
    return np.array(cropped)


if __name__ == '__main__':
    first_time=time.time()
    x = 144.73429951690818
    y = 76.13526570048306
    width = 640.0000000000001
    height = 640.0000000000001
    t_rotate = 90
    clip = VideoFileClip("head_cap.gif", has_mask=False)  # 在加载GIF时指定保留所有帧
    cropped_clip = clip.fl_image(pillow_movie_crop)
    cropped_clip.write_gif("21.gif", fuzz=10, opt='nq')  # 在写出GIF时指定优化参数,减少质量损
    print('总共用时:',time.time()-first_time,'秒')         # 4.687000274658203 秒 基本上4-5s左右

所以其实使用Pillow库是一个优选

Django 文件上传

启用 media

#settings.py

# 在末尾加入
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

#utils.py 自主封装的自定义文件字段,对上传文件进行限制

class RestrictedFileField(FileField):
    """ max_upload_size:
            2.5MB - 2621440
            5MB - 5242880
            10MB - 10485760
            20MB - 20971520
            50MB - 5242880
            100MB 104857600
            250MB - 214958080
            500MB - 429916160
    """

    def __init__(self, *args, **kwargs):
        self.content_types = kwargs.pop("content_types", [])
        self.max_upload_size = kwargs.pop("max_upload_size", [])
        super().__init__(*args, **kwargs)
     def clean(self, *args, **kwargs):
        data = super().clean(*args, **kwargs)  # clean()方法来自于FileField的父类Field, 用于验证

        file = data.file
        try:
        	content_type = file.content_type
            # 自定义验证
            if content_type in self.content_types:
                if file.size > self.max_upload_size:
                   raise forms.ValidationError('文件大小要求为{}. 当前文件大小为 {}'.format(filesizeformat(self.max_upload_size), filesizeformat(file.size)))	 
            else:
                raise forms.ValidationError('当前文件格式不被运行,仅支持{}.'.format(self.content_types))
        except AttributeError:
             pass
        return data

#models.py

from utils import RestrictedFileField
class UserInfo(models.Model)
	avatar = RestrictedFileField(verbose_name=u'头像', max_length=50, default='avatar/用户.png',
                             upload_to='avatar/',
                             content_types=['image/jpeg', 'image/png', 'image/gif', 'image/bmp',
                                            'image/tiff'],max_upload_size=5242880)
	id = models.BigAutoField(verbose_name='UID', primary_key=True)
……

#urls.py

from django.urls import path, include, re_path
from django.views.static import serve
from django.conf import settings
from . import views

urlpatterns = [
    # 上传文件位置
    re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}, name='media'),
    ……

#views.py

from django import forms
from app.error import errorResponse
class UserInfoForm(forms.ModelForm):
    # avatar = forms.FileField(label='头像')
    class Meta:
        model = UserInfo
        fields = '__all__'
def edit_info(request):
    uid = request.session['uid'] # 用户登录存储在session中的信息
    userinfo = UserInfo.objects.filter(id=uid).get()
    if request.method == 'GET':
        form = UserInfoForm(instance=userinfo)
        return render(request, 'space_edit_info.html', {
            'userinfo': userinfo,
            'form': form,
            'title': '个人中心-编辑个人信息'
        })
    form = UserInfoForm(instance=userinfo, data=request.POST)
    if form.is_valid():
        form.save()
        return redirect('/space/edit_info/')
    return errorResponse(request, errMsg=form['avatar'].errors[0] )

此处的errorResponse是自己封装的处理错误页函数,这样可以在前端显示自定义头像组件的错误

def errorResponse(request, errMsg):
    '''
    :param request:
    :param errMsg:返回错误信息到前端页面
    :return:
    '''
    return render(request, 'error.html', {'errMsg': errMsg})

Django 图片上传剪切综合案例实现

前端获取所需的坐标数据

使用基于JQuery的cropper和Bootstrap,可以更方便的实现获取用户对图片剪切的操作(缩放,旋转等)后的数据和坐标。

jQuery cropper是一款使用简单且功能强大的图片剪裁jquery插件。该插件支持图片放大,缩小,旋转,裁剪和预览等功能。

素材来源17素材,不用登录另存为网页就行。

html5头像图片上传截图保存修改代码 (17sucai.com)

在这里插入图片描述

注意演示里的最右上角x最好点一下,因为我们要的里面的标签的html文档,然后保存下来的就是我们需要的素材,然后自己处理一下。文件主要如下

在这里插入图片描述

sitelogo.js是利用cropper获取坐标等数据,然后携带数据向处理的url放送请求

目标的url又写在了签单的html文件里 from的action,修改这个即可。

这里我使用的是Django的url反向解析,更便利一些,有着不少好处。

在这里插入图片描述
而在sitelogo.js中,主要要去关注两部分:

这部分是序列化坐标和旋转角度数据,这里的x,y是左上的坐标,根据height和width可以计算出选中的矩形整个坐标。

在这里插入图片描述

发送ajax请求

在这里插入图片描述

前端的文件主要知道这些就可以了,自己不喜欢这些样式,想再多添加些功能可以自己改。

而前端重中之重就是记得Django有一个csrf安全验证机制,如果没有在中间件关闭它,就需要在前端添提交表单添加一个{% csrf_token %}或者在对应函数前面标记@csrf_exempt装饰器。再或者直接根据上面这张ajax提交代码的图片,在你自己写的js代码中添加在headers中.

@csrf_exempt

from django.views.decorators.csrf import csrf_exempt
#在对应函数前面添加装饰器
@csrf_exempt

后端根据坐标对图片进行剪切

#models.py 继承上面

#urls.py 继承上面且扩充

    path('upload_avatar/', views.upload_avatar, name='upload_avatar'),
    path('ajax_avatar_upload/', views.ajax_avatar_upload, name='ajax_avatar_upload'),

#views.py

import re
import os
import json
import uuid
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt
from django import forms
from PIL import Image, ImageSequence
from .models import UserInfo
# 这里使用的是Form而不是ModelForm,你可以直接引用上面写的UserInfo,用其自定义的头像字段,当然这里只是为了讲解和展示,你自己可以试试我说的
class AvatarUploadForm(forms.Form):
    avatar_file = forms.ImageField()
    
def upload_avatar(request):
	uid = request.session['uid']
    userinfo = UserInfo.objects.filter(id=uid).get()
    return render(request, 'Document.html', {
        'userinfo': userinfo
    })


def ajax_avatar_upload(request):
	uid = request.session['uid']
    userinfo = get_object_or_404(UserInfo, userName=username)

    if request.method == "POST":
        form = AvatarUploadForm(request.POST, request.FILES)
        if form.is_valid():
            img = request.FILES['avatar_file']  # 获取上传图片
            data = request.POST['avatar_data']  # 获取ajax返回图片坐标
            # print(img.size)  # 图片大小

            if img.size > 5242880:
              	# 相当于if img.size / 1024 / 1024 > 5:
                return JsonResponse({"message": "上传图片大小应小于5MB, 请重新上传。", })

            current_avatar = userinfo.avatar
            cropped_avatar = crop_image(current_avatar, img, data, userinfo.id)
            userinfo.avatar = cropped_avatar  # 将图片路径修改到当前会员数据库
            userinfo.save()
            # 向前台返回一个json,result值是图片路径
            data = {"result": userinfo.avatar.url, }
            return JsonResponse(data)

        else:
            return JsonResponse({"message": "请重新上传。只能上传图片"})

    return HttpResponseRedirect(reverse('upload_avatar'))


def crop_image(current_avatar, file, data, uid):
    '''

    :param current_avatar:  当前头像
    :param file:            上传文件
    :param data:            坐标
    :param uid:             用户id
    :return:                保存的图片路径名
    '''
    # 随机生成新的图片名,自定义路径。
    ext = file.name.split('.')[-1]
    file_name = '{}.{}'.format(uuid.uuid4().hex[:10], ext)
    cropped_avatar = os.path.join(str(uid), "avatar", file_name)
    # 相对根目录路径
    file_path = os.path.join("media", str(uid), "avatar", file_name)

    # 获取Ajax发送的裁剪参数data,先用json解析。
    coords = json.loads(data)
    t_x = int(coords['x'])
    t_y = int(coords['y'])
    t_width = t_x + int(coords['width'])
    t_height = t_y + int(coords['height'])
    t_rotate = coords['rotate']
	if abs(t_rotate)<=90:		#这里旋转小于等于90度居然是相反方向
        t_rotate=-t_rotate
    # 通过文件名判断是否为gif
    if file_name.endswith('.gif'):
        '''成功了就是保存的文件会变大,还有保存速度太慢了4~5s,前台应该做个动画'''
        frames = []

        with Image.open(file) as im:
            idx = 0
            for frame in ImageSequence.Iterator(im):
                frame = frame.crop((t_x, t_y, t_width, t_height)).resize((400, 400)).rotate(t_rotate)
                frame.info['duration'] = im.info['duration']
                frames.append(frame)
                idx += 1
        frames[0].save(file_path,
                       save_all=True, append_images=frames[1:], loop=0, duration=im.info['duration'], quality=80)
    else:

        # 裁剪图片,压缩尺寸为400*400。
        img = Image.open(file)
        # 由于我的Pillow版本是     10.0 Image.ANTIALIAS 被弃用
        crop_im = img.crop((t_x, t_y, t_width, t_height)).resize((400, 400), Image.LANCZOS).rotate(t_rotate)

        directory = os.path.dirname(file_path)
        if not os.path.exists(directory):
            os.makedirs(directory)
        crop_im.save(file_path)

    # 如果头像不是默认头像,删除老头像图片, 节省空间
    # 这部分可以删去,或者限制用户可以查看最近10张头像历史
    if not current_avatar == os.path.join("media", "avatar", "用户.png"):
        current_avatar_path = os.path.join("media", str(uid), "avatar",os.path.basename(current_avatar.url))
        os.remove(current_avatar_path)

    return cropped_avatar

参考文章:

Django+cropper实现用户头像裁剪, 预览和上传 - 大江狗的文章 - 知乎 https://zhuanlan.zhihu.com/p/41181618

在Django中如何限制上传文件的类型与大小_Sc.Crist的博客-优快云博客

总结:

当初自己整这个可是整了挺久的,很难找到直接拿来用的,很多都是前端VUE处理的,没有直接是Html或是Html+JQuery,而且那些头像处理没有对GiF处理的步骤,就观在那些大型网站来看,现在好像确实没有多少支持用gif动图为头像的。这里的轮子是自己造的。

虽然历经不少,也找到了解决办法,但是还是那么难找,整了挺久的。找到了还是要自己理解和运用,就这样吧,希望我的文章能给你提供帮助。

希望能够我一个赞同/赞/收藏辣(‾◡◝)

大家有什么问题可以在评论区大胆留盐(不用加密(bushi

与知乎同步发布,我应该在博客园也发一份~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值