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());
}
}