[Android][Django] ImageField로 선언한 필드의 base64 인코딩- Bitmap을 byteArray로 바꾸기

vector13·2021년 11월 24일
0

Android

목록 보기
8/12

장고에서 모델을 생성하고 그의 필드에 ImageField를 선언했을 때 , Android Studio와 연동되어
웹서버( 장고) ↔ 클라이언트(안드로이드 스튜디오)
웹서버( 장고) 측으로 클라이언트(안드로이드 스튜디오) 가 post요청을 보낼 시, ImageField로 선언되면 자동으로 걸러주는 필터링 기능이 있어보였다. (제대로 된 url주소가 아니면 post못하게 하는 것 같음)

웹서버( 장고) 측에서 클라이언트(안드로이드 스튜디오) 으로 GET을 보낼 때는 bitmap으로 바로 (url형태로 온 json값을 Bitmap으로 출력) 보여줄 수 있었으나,
POST로 보낼 때가 문제였다.

그러다 해답을 찾은 아이디어는, Django 쪽 모델의 serializer부분을 변경해주고, Android Studio에서 Bitmap(url)을 byteArray로 바꿔 이것을 base64로 인코딩 해주는 방법이었다.

아이디어를 구현하기 위해 코드를 보면

serializer.py

from rest_framework import serializers

from .models import calendar, user, diary
class Base64ImageField(serializers.ImageField):   

    def to_internal_value(self, data):
        from django.core.files.base import ContentFile
        import base64
        import six
        import uuid

       
        if isinstance(data, six.string_types):
            # Check if the base64 string is in the "data:" format
            if 'data:' in data and ';base64,' in data:
                # Break out the header from the base64 content
                header, data = data.split(';base64,')           
            try:
                decoded_file = base64.b64decode(data)
            except TypeError:
                self.fail('invalid_image')
            
            file_name = str(uuid.uuid4())[:12] 
           
            file_extension = self.get_file_extension(file_name, decoded_file)

            complete_file_name = "%s.%s" % (file_name, file_extension, )

            data = ContentFile(decoded_file, name=complete_file_name)

        return super(Base64ImageField, self).to_internal_value(data)

    def get_file_extension(self, file_name, decoded_file):
        import imghdr

        extension = imghdr.what(file_name, decoded_file)
        extension = "jpg" if extension == "jpeg" else extension

        return extension


class DiarySerializer(serializers.ModelSerializer):
    diary_img = Base64ImageField(
        max_length=None, use_url=True, required=False
    )
    class Meta:
        model = diary 
        fields = ('diary_id', 'diary_title', 'diary_date', 'diary_weather', 'diary_content', 'diary_todayme', 'diary_tomorrowme', 'diary_img', )

빼먹지 않아야하는 부분은, models.py에서 diary_img 필드를 ImageField(null = True, blank = True,) 로 선언했고, serializers.py에서는 required=False 값을 줘야 원래 의도대로 이미지가 입력되지 않아도 (null)이어도 status code 400 Bad request 'No File submitted' 등의 오류를 만나지 않는다.

models.py

class diary(models.Model):
    diary_id = models.AutoField(primary_key = True)
    diary_date = models.DateTimeField(auto_now_add = True)
    diary_weather = models.IntegerField()
    diary_title = models.TextField(max_length = None,)
    diary_content = models.TextField(max_length = None)
    diary_todayme = models.TextField(max_length = None, null = True, blank = True)
    diary_tomorrowme = models.TextField(max_length = None, null = True, blank = True)
    diary_img = models.ImageField(null = True, blank = True,)
#    user = models.ForeignKey(user, on_delete = models.CASCADE, null = True)
    def __str__(self):
        return self.diary_title

이제 요청 측인 안드로이드 스튜디오에서
갤러리를 통한 사진 (data)를 가져오고, intent가 들어오면 onActivityResult()가 호출되도록 설계한다.

이 전에 갤러리 모양의 버튼 xml을 만들고 setOnClickListener를 이용해 메소드를 호출하게 만든다.

//사진 추가 버튼 클릭 시
        galleyButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                imageUpload(view);
            }
        });

WriteFragment.java

//사진 등록 메소드
    //이미지 업로드
    public void imageUpload(View view) {
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType(MediaStore.Images.Media.CONTENT_TYPE);
        startActivityForResult(intent, 101);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == 101) {
            if (resultCode == RESULT_OK) {
                try {
                    //데이터 타입 data.getData() : uri
                    InputStream in = getContext().getContentResolver().openInputStream(data.getData());
                    
                    Bitmap img = BitmapFactory.decodeStream(in);
                    in.close();
                    write_picked_imgview.setVisibility(View.VISIBLE);
                    write_picked_imgview.setImageBitmap(img);
                    //Bitmap에서 byteArray
                    byterray = bitmapToByteArray(img);
                    //byteArray를 base64로 인코딩해서 diary_img에 넣기
                    diary_img = Base64.encodeToString(byterray, Base64.NO_WRAP);
                } catch (Exception e) {

                }
            } else if (resultCode == RESULT_CANCELED) {
                Toast.makeText(getContext(), "사진 선택 취소 ", Toast.LENGTH_LONG).show();
            }
        }
    }    

    // Bitmap을 Byte로 변환
    public byte[] bitmapToByteArray(Bitmap bitmap) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        byte[] byteArray = stream.toByteArray();
        return byteArray;
    }

코드를 보면 imageUpload메소드로 업로드된 이미지를 intent에 저장하고, startActivityForResult()를 호출,
onActivityResult메소드에서는 url타입을 되어있는 Bitmap 데이터 img를 밑에 선언한 메소드인 bitmapToByteArray를 이용해 byteArray타입의 이진파일 byterray 로 바꾸고, Base64.encodeToString를 이용해 이진파일을 모델의 필드 변수에 저장해 post를 보내면 된다.

위에서 serializers.py에서 required=False로 했기 때문에, diary_img는 null이 가능하고, 생성자를 통해 Call객체로 POST요청이 정상적으로 가게된다.

profile
HelloWorld! 같은 실수를 반복하지 말기위해 적어두자..

0개의 댓글