sitelink1  
sitelink2  
sitelink3  
sitelink4  
extra_vars4  
extra_vars5  
extra_vars6  

html5 와 servlet 으로 구현한 샘플이다.

카메라로 사진을 캡춰하는 방식이 두가지로 조사되었다.

첫번째는 아주 간단하게 type 이 file 인 input elelment 를 이용하는 방법이고

두번째는 Web RTC를 이용하여 video element 로 부터 이미지 캡춰하는 방법이다.

 

1. file type input element 로 사진 촬영

    input element 에서 type을 file 로 하고 파일 지정시 카메라를 선택하면 사진을 찍을 수 있다.

    카메라를 활성화 하여 사진을 찍은후 canvas 로 image 를 옮긴후

    canvas 의 image 를 blob 으로 변환하여 서버로 multipart 전송하여 저장 처리

 

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Camera File to Canvas</title>

</head>

<body>

  <h1>Take a Photo and Upload</h1>

  

  <!-- File Input and Upload Button -->

  <input type="file" id="fileInput" accept="image/*" capture="environment">

  <br>

  <button id="uploadButton">Upload to Server</button>

  <br><br>

  

  <!-- Canvas -->

  <canvas id="canvas" style="border: 1px solid black;"></canvas>

  <br><br>

 

  

 

  <script>

    const fileInput = document.getElementById('fileInput');

    const canvas = document.getElementById('canvas');

    const ctx = canvas.getContext('2d');

    const uploadButton = document.getElementById('uploadButton');

 

    fileInput.addEventListener('change', (event) => {

      const file = event.target.files[0];

      if (file) {

        const reader = new FileReader();

        reader.onload = function (e) {

          const img = new Image();

          img.onload = function () {

            // Set canvas size to image size

            canvas.width = img.width;

            canvas.height = img.height;

            ctx.drawImage(img, 0, 0);

          };

          img.src = e.target.result;

        };

        reader.readAsDataURL(file);

      }

    });

 

    uploadButton.addEventListener('click', () => {

        canvas.toBlob(blob => {

            const xhr = new XMLHttpRequest();

            xhr.open('POST', '/jbs/test_picup', true); // 서버의 업로드 엔드포인트

            xhr.setRequestHeader('Content-Type', 'application/octet-stream');

            xhr.onreadystatechange = () => {

                if (xhr.readyState === 4 && xhr.status === 200) {

                    alert('Image uploaded successfully!! ' + xhr.responseText);

                }

            };

            xhr.send(blob);

        }, 'image/png'); // PNG 포맷으로 Blob 생성

    });

  </script>

</body>

</html>

    file type input element 를 이용하여 사진을 찍는게 가장 단순하고 명확하면서 정확한 동작이 가능한 방법이다.

    심지어 디바이스에 카메라가 없어도 오류를 일으킬 필요없이 그냥 저장된 파일을 지정하게 하면 되므로 사용자 친화적인 방식이다.

 

 

2. 카메라로 연속 촬영하는 video element 로 이미지를 캡춰하여 저장

    카메라를 videoinput 으로 시작하여 스트림에서 image를 캡춰하여 canvas 로 옮긴후

    canvas 의 image 를 blob 으로 변환하여 서버로 multipart 전송하여 저장 처리

 

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Camera Capture and Upload</title>

    <style>

        video {

            display: block;

            margin: 10px auto;

            object-fit: cover; /* 잘라내기를 허용 */

            width: 100vw; /* 화면 너비에 맞추기 */

            height: 100vw; /* 1:1 비율 유지 */

            max-width: 500px; /* 최대 크기 제한 */

            max-height: 500px;

            transform: scale(1);

            border: 1px solid #ddd;

        }

        canvas {

            display: block;

            margin: 10px auto;

            border: 1px solid #ddd;

        }

        button {

            display: block;

            margin: 10px auto;

            padding: 10px 20px;

            font-size: 16px;

        }

    </style>

</head>

<body>

    <h1>Camera Capture and Upload</h1>

    <video id="video" autoplay playsinline controls width="640" height="480" style="border:1px solid black;"></video>

    <canvas id="canvas" width="640" height="480" style="display:none;"></canvas>

    <button id="capture">Capture Photo</button>

    <button id="upload" style="display:none;">Upload Photo</button>

 

    <script type="text/javascript" src="https://unpkg.com/@zxing/library@latest/umd/index.min.js"></script>

    <script>

        const video = document.getElementById('video');

        const canvas = document.getElementById('canvas');

        const captureButton = document.getElementById('capture');

        const uploadButton = document.getElementById('upload');

 

        // 카메라 시작

        navigator.mediaDevices.enumerateDevices()

            .then(devices => {

                const videoDevices = devices.filter(device => device.kind === 'videoinput');

                const backCamera = videoDevices.find(device => device.label.toLowerCase().includes('back'));

 

                // 카메라 활성화 옵션 설정

                const cameraOptions = {

                    video: {

                        width: { exact: 1920 },  // 원하는 너비 (예: 1920px)

                        height: { exact: 1080 }, // 원하는 높이 (예: 1080px)

                        facingMode: 'environment' // 또는 'rear'로 후면 카메라 설정 (전면 카메라는 user로 설정)

                    },

                    audio: false // 오디오는 필요 없다고 가정

                };

 

                if (backCamera) {

                    cameraOptions.video.deviceId = { exact: backCamera.deviceId };

                }

                return navigator.mediaDevices.getUserMedia(cameraOptions);

            })

            .then(stream => {

                // 줌 초기화 시도 (옵션으로 제공해주는 카메라에서만 가능)

                const track = stream.getVideoTracks()[0];

                const capabilities = track.getCapabilities();                

                if ('zoom' in capabilities) {

                    //chrome, edge 에서 지원하는 것을 확인함(firefox 는 지원 안함)

                    const constraints = { advanced: [{ zoom: 1.0 }] }; // 줌 기본값 1.0 설정

                    track.applyConstraints(constraints).catch((error) => {

                        console.error('Failed to set zoom:', error);

                    });

                }

                video.srcObject = stream;

            })

            .catch(err => {

                console.error("Error accessing the camera: ", err);

                alert("일반 후면 카메라를 사용할 수 없습니다. 브라우저 설정을 확인하세요.");

            });

 

        // 사진 캡처

        captureButton.addEventListener('click', () => {

            const ctx = canvas.getContext('2d');

            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

            uploadButton.style.display = 'inline';

        });

 

        // 서버로 바이너리 데이터 전송

        uploadButton.addEventListener('click', () => {

            canvas.toBlob(blob => {

                const xhr = new XMLHttpRequest();

                xhr.open('POST', '/jbs/test_picup', true); // 서버의 업로드 엔드포인트

                xhr.setRequestHeader('Content-Type', 'application/octet-stream');

                xhr.onreadystatechange = () => {

                    if (xhr.readyState === 4 && xhr.status === 200) {

                        alert('Image uploaded successfully!! ' + xhr.responseText);

                    }

                };

                xhr.send(blob);

            }, 'image/png'); // PNG 포맷으로 Blob 생성

        });

    </script>

</body>

</html>

    위와 같이 video element 를 사용할때 까다로운 점 하나가 있었는데

    카메라들이 전면, 후면 등으로 여러개이다보니 어떤 카메라를 이용할지 지정해야만 했고

    카메라가 없는 디바이스는 에외 처리로 오류 메세지 출력도 해줘야 했다.

    그리고 시스템에 따른 디바이스 디폴트 값이 원하는 형태가 아니어서 줌 같은 경우 0.5 배율이 기본이어서 이를 1.0 으로 변환하는 방법을 찾았지만

    결국 1.0 배율로 변경하지 못한 버그가 있다.

    그럼에도 불구하고 이 방식이 꼭 필요한 경우가 있을테니 그때를 위해 참고로 기록해 둔다.

 

 

서블릿에서는 위와 같이 전송한 blob 데이터를 multipart 로 수신하여 처리한다.

 

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

 

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

 

@SuppressWarnings("serial")

@WebServlet(name = "PictureUpTestServlet", urlPatterns = { "/test_picup" }, loadOnStartup = 1)

public class PictureUpTestServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

 

    @Override

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 저장할 디렉토리 설정

        String uploadDir = getServletContext().getRealPath("/") + "uploads";

        File dir = new File(uploadDir);

        if (!dir.exists()) {

            dir.mkdirs(); // 디렉토리가 없으면 생성

        }

 

        // 파일 저장

        File outputFile = new File(uploadDir, "captured_image.png");

        try (InputStream inputStream = request.getInputStream();

             FileOutputStream outputStream = new FileOutputStream(outputFile)) {

            byte[] buffer = new byte[1024];

            int bytesRead;

            while ((bytesRead = inputStream.read(buffer)) != -1) {

                outputStream.write(buffer, 0, bytesRead);

            }

        }

 

        // 응답

        response.setContentType("text/plain");

        response.getWriter().write("Image uploaded to: " + outputFile.getAbsolutePath());

    }

}

 

 

 

 
번호 제목 글쓴이 날짜 조회 수
246 [Gemini] ajax 와 fetch 의 차이 황제낙엽 2025.01.04 205
» [검토 완료] 디바이스 카메라로 촬영한 사진을 서버에 업로드하기 샘플 황제낙엽 2024.11.21 310
244 브라우저에서 이미지를 편집(crop 등) 할 수 있는 오픈소스 Cropper.js 황제낙엽 2024.11.16 341
243 [ChatGPT] json data 의 정렬 [1] 황제낙엽 2024.07.23 204
242 DOMContentLoaded 이벤트와 window.onload 이벤트 황제낙엽 2024.07.15 231
241 fetch() 사용 예제들 황제낙엽 2024.07.09 263
240 [Copilot] Vanilla JavaScript에서 외부 스크립트 파일에 정의된 함수들을 로드 황제낙엽 2024.06.21 275
239 [Copilot] ES6 모듈(module) 문법 황제낙엽 2024.06.21 219
238 [Copilot] JSON 객체 내부 데이터를 순회하면서 조회하는 코드 황제낙엽 2024.06.02 213
237 [Copilot] JavaScript로 서버에 특정 값을 POST 방식으로 전달하고 응답을 기다리지 않고 바로 종료 황제낙엽 2024.05.31 393
236 json 데이터 내의 변수명에 prefix 로 type 표현하기 황제낙엽 2024.04.15 210
235 fetch() 함수 사용 예제 file 황제낙엽 2023.11.23 435
234 현재 document 의 host 와 port 를 얻는 방법 황제낙엽 2023.10.03 668
233 (Bard) FileReader 로 여러개의 파일을 read 하는 법 file 황제낙엽 2023.08.23 468
232 How to build a file upload service with vanilla JavaScript file 황제낙엽 2023.08.22 287
231 (Bard) JavaScript로 JSON 배열을 작성하는 방법 황제낙엽 2023.08.21 235
230 모바일 브라우저에서 file input element 를 이용하여 여러장의 이미지를 서버에 전송하려 할때 [1] 황제낙엽 2023.08.21 318
229 navigator.mediaDevices 황제낙엽 2023.08.21 294
228 Barcode Detection API 황제낙엽 2023.08.06 513
227 정규식을 이용한 이메일 검증 스크립트 file 황제낙엽 2023.06.25 199