So I have a form, and in this form a user can upload a picture. As an alternative, I want them to be able to take a picture and upload that instead.
Now I figure that when a user takes a picture with some sort of flash webcam interface, that picture needs to be stored in some sort of intermediary memory while the user completes his/her form.
Is there a way around this? What's the best solution?
I figure there are only really two options.
Either (a) I hold the temporary photo and then accept it again once the form is submitted or (b) the user takes a picture, it's downloaded onto their local machine, and they have to choose it as a file to be submitted again.
Neither of these solutions are really satisfactory to me, so I wanted to know if there's any better way to do this.
Edit: As an added bonus, although it's not really relevant, I'm making a Rails app with Paperclip for file attachments. Also, I prefer to work with jQuery...
Also just general opinions from experienced UI web developers would be nice..
I'd used this: http://www.xarg.org/project/jquery-webcam-plugin/ in my rails application for capturing an image from a webcam. You can download this jQuery-webcam here: https://github.com/infusion/jQuery-webcam
Here is a roughly done implementation, sorry for the messy code:
class PicturesController < ApplicationController
require 'base64'
def capture
# do something
render :layout => "webcam"
end
def save_image
image = params[:capture][:image]
File.open("#{Rails.root}/public/path_you_want_to_image/image_name.png", 'wb') do |f|
f.write(Base64.decode64(image))
end
# Or use paperclip to save image for a model instead!!
end
end
views/layouts/webcam.html.erb:
<!DOCTYPE html>
<html>
<head>
<title>Application Name</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= javascript_include_tag "http://www.google-analytics.com/ga.js"%>
<%= javascript_include_tag "http://code.jquery.com/jquery-1.4.2.min.js"%>
<%= javascript_include_tag "jquery.webcam"%>
<script>
!window.jQuery && document.write('<script src="jquery-1.4.3.min.js"><\/script>');
</script>
<%= csrf_meta_tag %>
</head>
<body>
<%= yield %>
</body>
</html>
views/pictures/capture.html.erb:
<div id="webcam">
<p>Capture image here!</p>
</div>
<%= form_for(save_image_pictures_path, :method => "post", :remote => true) do |f|%>
<%= hidden_field(:item, :sku)%>
<div id="capture_images"><input id="capture_image" type="hidden" value="" name="capture[image]"></div>
<%= submit_tag "Capture Image", :onClick=>"javascript:capture_image();"%>
<% end %>
<%= link_to "Back", root_path %>
<p>
<canvas id="canvas" width="320" height="240"></canvas>
</p>
<script type="text/javascript">
var pos = 0;
var ctx = null;
var cam = null;
var image = null;
var filter_on = false;
var filter_id = 0;
function changeFilter() {
if (filter_on) {
filter_id = (filter_id + 1) & 7;
}
}
function capture_image(){
webcam.capture();
changeFilter();
void(0);
var canvas = document.getElementById('canvas')
var context = canvas.getContext("2d");
var img = canvas.toDataURL("image/png");
var item_image = img.replace(/^data:image\/(png|jpg);base64,/, "") ;
document.getElementById('capture_images').innerHTML="<input id=\"capture_image\" type=\"hidden\" value=\""+item_image+"\" name=\"capture[image]\">";
}
function toggleFilter(obj) {
if (filter_on =!filter_on) {
obj.parentNode.style.borderColor = "#c00";
} else {
obj.parentNode.style.borderColor = "#333";
}
}
jQuery("#webcam").webcam({
width: 320,
height: 240,
mode: "callback",
swffile: "/javascripts/jscam_canvas_only.swf",
onTick: function(remain) {
if (0 == remain) {
jQuery("#status").text("Cheese!");
} else {
jQuery("#status").text(remain + " seconds remaining...");
}
},
onSave: function(data) {
var col = data.split(";");
var img = image;
if (false == filter_on) {
for(var i = 0; i < 320; i++) {
var tmp = parseInt(col[i]);
img.data[pos + 0] = (tmp >> 16) & 0xff;
img.data[pos + 1] = (tmp >> 8) & 0xff;
img.data[pos + 2] = tmp & 0xff;
img.data[pos + 3] = 0xff;
pos+= 4;
}
} else {
var id = filter_id;
var r,g,b;
var r1 = Math.floor(Math.random() * 255);
var r2 = Math.floor(Math.random() * 255);
var r3 = Math.floor(Math.random() * 255);
for(var i = 0; i < 320; i++) {
var tmp = parseInt(col[i]);
/* Copied some xcolor methods here to be faster than calling all methods inside of xcolor and to not serve complete library with every req */
if (id == 0) {
r = (tmp >> 16) & 0xff;
g = 0xff;
b = 0xff;
} else if (id == 1) {
r = 0xff;
g = (tmp >> 8) & 0xff;
b = 0xff;
} else if (id == 2) {
r = 0xff;
g = 0xff;
b = tmp & 0xff;
} else if (id == 3) {
r = 0xff ^ ((tmp >> 16) & 0xff);
g = 0xff ^ ((tmp >> 8) & 0xff);
b = 0xff ^ (tmp & 0xff);
} else if (id == 4) {
r = (tmp >> 16) & 0xff;
g = (tmp >> 8) & 0xff;
b = tmp & 0xff;
var v = Math.min(Math.floor(.35 + 13 * (r + g + b) / 60), 255);
r = v;
g = v;
b = v;
} else if (id == 5) {
r = (tmp >> 16) & 0xff;
g = (tmp >> 8) & 0xff;
b = tmp & 0xff;
if ((r+= 32) < 0) r = 0;
if ((g+= 32) < 0) g = 0;
if ((b+= 32) < 0) b = 0;
} else if (id == 6) {
r = (tmp >> 16) & 0xff;
g = (tmp >> 8) & 0xff;
b = tmp & 0xff;
if ((r-= 32) < 0) r = 0;
if ((g-= 32) < 0) g = 0;
if ((b-= 32) < 0) b = 0;
} else if (id == 7) {
r = (tmp >> 16) & 0xff;
g = (tmp >> 8) & 0xff;
b = tmp & 0xff;
r = Math.floor(r / 255 * r1);
g = Math.floor(g / 255 * r2);
b = Math.floor(b / 255 * r3);
}
img.data[pos + 0] = r;
img.data[pos + 1] = g;
img.data[pos + 2] = b;
img.data[pos + 3] = 0xff;
pos+= 4;
}
}
if (pos >= 0x4B000) {
ctx.putImageData(img, 0, 0);
pos = 0;
}
},
onCapture: function () {
webcam.save('/product_capture');
jQuery("#flash").css("display", "block");
jQuery("#flash").fadeOut(100, function () {
jQuery("#flash").css("opacity", 1);
});
},
debug: function (type, string) {
jQuery("#status").html(type + ": " + string);
},
onLoad: function () {
var cams = webcam.getCameraList();
for(var i in cams) {
jQuery("#cams").append("<li>" + cams[i] + "</li>");
}
}
});
function getPageSize() {
var xScroll, yScroll;
if (window.innerHeight && window.scrollMaxY) {
xScroll = window.innerWidth + window.scrollMaxX;
yScroll = window.innerHeight + window.scrollMaxY;
} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
xScroll = document.body.scrollWidth;
yScroll = document.body.scrollHeight;
} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
xScroll = document.body.offsetWidth;
yScroll = document.body.offsetHeight;
}
var windowWidth, windowHeight;
if (self.innerHeight) { // all except Explorer
if(document.documentElement.clientWidth){
windowWidth = document.documentElement.clientWidth;
} else {
windowWidth = self.innerWidth;
}
windowHeight = self.innerHeight;
} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
windowWidth = document.documentElement.clientWidth;
windowHeight = document.documentElement.clientHeight;
} else if (document.body) { // other Explorers
windowWidth = document.body.clientWidth;
windowHeight = document.body.clientHeight;
}
// for small pages with total height less then height of the viewport
if(yScroll < windowHeight){
pageHeight = windowHeight;
} else {
pageHeight = yScroll;
}
// for small pages with total width less then width of the viewport
if(xScroll < windowWidth){
pageWidth = xScroll;
} else {
pageWidth = windowWidth;
}
return [pageWidth, pageHeight];
}
window.addEventListener("load", function() {
jQuery("body").append("<div id=\"flash\"></div>");
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
ctx = document.getElementById("canvas").getContext("2d");
ctx.clearRect(0, 0, 320, 240);
var img = new Image();
img.src = "/images/rails.png";
img.onload = function() {
ctx.drawImage(img, 129, 89);
}
image = ctx.getImageData(0, 0, 320, 240);
}
var pageSize = getPageSize();
jQuery("#flash").css({ height: pageSize[1] + "px" });
}, false);
window.addEventListener("resize", function() {
var pageSize = getPageSize();
jQuery("#flash").css({ height: pageSize[1] + "px" });
}, false);
</script>
Just make sure that, "jscam.swf" file referenced in jquery.webcam.js is loaded on page properly.
routes you can define in your routes.rb are:
resources :pictures do
collection do
get 'capture'
post 'save_image'
end
end
From here, you can then use this: https://github.com/blueimp/jQuery-File-Upload for uploading it using an Ajax form submission!!
Do let me know if you have any questions.
Errors.
Replace
def save_image
image = params[:canvas][:image]
File.open("#{Rails.root}/public/path_you_want_to_image/image_name.png", 'wb') do |f|
f.write(Base64.decode64(image))
end
# Or use paperclip to save image for a model instead!!
end
by the
def save_image
image = params[:capture][:image]
File.open("#{Rails.root}/public/path_you_want_to_image/image_name.png", 'wb') do |f|
f.write(Base64.decode64(image))
end
# Or use paperclip to save image for a model instead!!
end
and
function capture_image(){
webcam.capture();
changeFilter();
void(0);
var canvas = document.getElementById('canvas')
var context = canvas.getContext("2d");
var img = canvas.toDataURL("image/png");
var item_image = img.replace(/^data:image\/(png|jpg);base64,/, "") ;
document.getElementById('capture_images').innerHTML="<input id=\"capture_image\" type=\"hidden\" value=\""+capture_image+"\" name=\"capture[image]\">";}
to
function capture_image(){
webcam.capture();
changeFilter();
void(0);
var canvas = document.getElementById('canvas')
var context = canvas.getContext("2d");
var img = canvas.toDataURL("image/png");
var item_image = img.replace(/^data:image\/(png|jpg);base64,/, "") ;
document.getElementById('capture_images').innerHTML="<input id='capture_image' type='hidden' value=\'"+item_image+"\' name='capture[image]'>";}
After this, all work fine.
PS. If you got: "capture_image() is not a f-n", change f-n name to: "capture_images()" don't forget change name on the onClick event