diff --git a/README.md b/README.md index 8438a31..e7dd1a8 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,18 @@ Usage: cam2ip [] Frame width [CAM2IP_WIDTH] (default "640") --height Frame height [CAM2IP_HEIGHT] (default "480") + --quality + Image quality [CAM2IP_QUALITY] (default "75") --rotate Rotate image, valid values are 90, 180, 270 [CAM2IP_ROTATE] (default "0") + --flip + Flip image, valid values are horizontal and vertical [CAM2IP_FLIP] (default "") --no-webgl - Disable WebGL drawing of images (html handler) [CAM2IP_NO_WEBGL] (default "false") + Disable WebGL drawing of image (html handler) [CAM2IP_NO_WEBGL] (default "false") --timestamp - Draws timestamp on images [CAM2IP_TIMESTAMP] (default "false") + Draws timestamp on image [CAM2IP_TIMESTAMP] (default "false") + --time-format + Time format [CAM2IP_TIME_FORMAT] (default "2006-01-02 15:04:05") --bind-addr Bind address [CAM2IP_BIND_ADDR] (default ":56000") --htpasswd-file diff --git a/camera/camera.go b/camera/camera.go index 3e3e062..2d08b04 100644 --- a/camera/camera.go +++ b/camera/camera.go @@ -2,9 +2,11 @@ package camera // Options . type Options struct { - Index int - Rotate int - Width float64 - Height float64 - Timestamp bool + Index int + Rotate int + Flip string + Width float64 + Height float64 + Timestamp bool + TimeFormat string } diff --git a/camera/camera_linux.go b/camera/camera_linux.go index ee1a922..a491f5c 100644 --- a/camera/camera_linux.go +++ b/camera/camera_linux.go @@ -92,6 +92,10 @@ func (c *Camera) Read() (img image.Image, err error) { img = im.Rotate(img, c.opts.Rotate) } + if c.opts.Flip != "" { + img = im.Flip(img, c.opts.Flip) + } + if c.opts.Timestamp { img, err = im.Timestamp(img, "") } diff --git a/camera/camera_opencv.go b/camera/camera_opencv.go index cc90ac8..1c2a730 100644 --- a/camera/camera_opencv.go +++ b/camera/camera_opencv.go @@ -69,6 +69,10 @@ func (c *Camera) Read() (img image.Image, err error) { img = im.Rotate(img, c.opts.Rotate) } + if c.opts.Flip != "" { + img = im.Flip(img, c.opts.Flip) + } + if c.opts.Timestamp { img, err = im.Timestamp(img, "") } diff --git a/camera/camera_windows.go b/camera/camera_windows.go index 587308c..1120849 100644 --- a/camera/camera_windows.go +++ b/camera/camera_windows.go @@ -34,11 +34,6 @@ func New(opts Options) (camera *Camera, err error) { camera.opts = opts camera.className = "capWindowClass" - camera.instance, err = getModuleHandle() - if err != nil { - return - } - camera.frame = image.NewRGBA(image.Rect(0, 0, int(camera.opts.Width), int(camera.opts.Height))) go func(c *Camera) { @@ -56,12 +51,18 @@ func New(opts Options) (camera *Camera, err error) { return 0 } + c.instance, err = getModuleHandle() + if err != nil { + return + } + err = registerClass(c.className, c.instance, fn) if err != nil { return } - hwnd, err := createWindow(0, c.className, "", wsOverlappedWindow, cwUseDefault, cwUseDefault, int64(c.opts.Width)+100, int64(c.opts.Height)+100, 0, 0, c.instance) + hwnd, err := createWindow(0, c.className, "", wsOverlappedWindow, cwUseDefault, cwUseDefault, + int64(c.opts.Width)+100, int64(c.opts.Height)+100, 0, 0, c.instance) if err != nil { return } @@ -92,7 +93,18 @@ func New(opts Options) (camera *Camera, err error) { sendMessage(c.camera, wmCapSetCallbackFrame, 0, syscall.NewCallback(c.callback)) - messageLoop(c.camera) + for { + var msg msgW + ok, _ := getMessage(&msg, hwnd, 0, 0) + if ok { + //translateMessage(&msg) + dispatchMessage(&msg) + } else { + break + } + } + + return }(camera) return @@ -139,20 +151,14 @@ func (c *Camera) Read() (img image.Image, err error) { img = im.Rotate(img, c.opts.Rotate) } - if c.opts.Timestamp { - img, err = im.Timestamp(img, "") + if c.opts.Flip != "" { + img = im.Flip(img, c.opts.Flip) } - return -} + if c.opts.Timestamp { + img, err = im.Timestamp(img, c.opts.TimeFormat) + } -// GetProperty returns the specified camera property. -func (c *Camera) GetProperty(id int) float64 { - return 0 -} - -// SetProperty sets a camera property. -func (c *Camera) SetProperty(id int, value float64) { return } @@ -321,8 +327,9 @@ func destroyWindow(hwnd syscall.Handle) error { // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-defwindowprocw func defWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr { - ret, _, _ := defWindowProcW.Call(uintptr(hwnd), uintptr(msg), uintptr(wparam), uintptr(lparam)) - return uintptr(ret) + ret, _, _ := defWindowProcW.Call(uintptr(hwnd), uintptr(msg), wparam, lparam) + + return ret } // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-dispatchmessagew @@ -348,6 +355,7 @@ func getMessage(msg *msgW, hwnd syscall.Handle, msgFilterMin, msgFilterMax uint3 // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendmessage func sendMessage(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr { ret, _, _ := sendMessageW.Call(uintptr(hwnd), uintptr(msg), wparam, lparam, 0, 0) + return ret } @@ -375,6 +383,7 @@ func registerClass(className string, instance syscall.Handle, fn interface{}) er // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-unregisterclassw func unregisterClass(className string, instance syscall.Handle) bool { ret, _, _ := unregisterClassW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(className))), uintptr(instance)) + return ret != 0 } @@ -388,19 +397,3 @@ func capCreateCaptureWindow(lpszWindowName string, dwStyle, x, y, width, height return syscall.Handle(ret), nil } - -// messageLoop function -func messageLoop(hwnd syscall.Handle) { - for { - msg := &msgW{} - ok, _ := getMessage(msg, 0, 0, 0) - if ok { - translateMessage(msg) - dispatchMessage(msg) - } else { - break - } - } - - return -} diff --git a/cmd/cam2ip/main.go b/cmd/cam2ip/main.go index 985cd24..3ef6ade 100644 --- a/cmd/cam2ip/main.go +++ b/cmd/cam2ip/main.go @@ -21,17 +21,21 @@ func main() { flag.IntVar(&srv.Index, "index", 0, "Camera index [CAM2IP_INDEX]") flag.IntVar(&srv.Delay, "delay", 10, "Delay between frames, in milliseconds [CAM2IP_DELAY]") - flag.Float64Var(&srv.FrameWidth, "width", 640, "Frame width [CAM2IP_WIDTH]") - flag.Float64Var(&srv.FrameHeight, "height", 480, "Frame height [CAM2IP_HEIGHT]") + flag.Float64Var(&srv.Width, "width", 640, "Frame width [CAM2IP_WIDTH]") + flag.Float64Var(&srv.Height, "height", 480, "Frame height [CAM2IP_HEIGHT]") + flag.IntVar(&srv.Quality, "quality", 75, "Image quality [CAM2IP_QUALITY]") flag.IntVar(&srv.Rotate, "rotate", 0, "Rotate image, valid values are 90, 180, 270 [CAM2IP_ROTATE]") - flag.BoolVar(&srv.NoWebGL, "no-webgl", false, "Disable WebGL drawing of images (html handler) [CAM2IP_NO_WEBGL]") - flag.BoolVar(&srv.Timestamp, "timestamp", false, "Draws timestamp on images [CAM2IP_TIMESTAMP]") + flag.StringVar(&srv.Flip, "flip", "", "Flip image, valid values are horizontal and vertical [CAM2IP_FLIP]") + flag.BoolVar(&srv.NoWebGL, "no-webgl", false, "Disable WebGL drawing of image (html handler) [CAM2IP_NO_WEBGL]") + flag.BoolVar(&srv.Timestamp, "timestamp", false, "Draws timestamp on image [CAM2IP_TIMESTAMP]") + flag.StringVar(&srv.TimeFormat, "time-format", "2006-01-02 15:04:05", "Time format [CAM2IP_TIME_FORMAT]") flag.StringVar(&srv.Bind, "bind-addr", ":56000", "Bind address [CAM2IP_BIND_ADDR]") flag.StringVar(&srv.Htpasswd, "htpasswd-file", "", "Path to htpasswd file, if empty auth is disabled [CAM2IP_HTPASSWD_FILE]") flag.Usage = func() { stderr("Usage: %s []\n", name) - order := []string{"index", "delay", "width", "height", "rotate", "no-webgl", "timestamp", "bind-addr", "htpasswd-file"} + order := []string{"index", "delay", "width", "height", "quality", "rotate", "flip", "no-webgl", + "timestamp", "time-format", "bind-addr", "htpasswd-file"} for _, name := range order { f := flag.Lookup(name) @@ -55,11 +59,13 @@ func main() { } cam, err := camera.New(camera.Options{ - Index: srv.Index, - Rotate: srv.Rotate, - Width: srv.FrameWidth, - Height: srv.FrameHeight, - Timestamp: srv.Timestamp, + Index: srv.Index, + Rotate: srv.Rotate, + Flip: srv.Flip, + Width: srv.Width, + Height: srv.Height, + Timestamp: srv.Timestamp, + TimeFormat: srv.TimeFormat, }) if err != nil { stderr("%s\n", err.Error()) diff --git a/handlers/html.go b/handlers/html.go index 15b6aa6..c88aea8 100644 --- a/handlers/html.go +++ b/handlers/html.go @@ -12,11 +12,11 @@ type HTML struct { } // NewHTML returns new HTML handler. -func NewHTML(width, height float64, nogl bool) *HTML { +func NewHTML(width, height float64, noWebGL bool) *HTML { h := &HTML{} tpl := htmlWebGL - if nogl { + if noWebGL { tpl = html } tpl = strings.Replace(tpl, "{WIDTH}", fmt.Sprintf("%.0f", width), -1) diff --git a/handlers/jpeg.go b/handlers/jpeg.go index c7b161d..06f1fb2 100644 --- a/handlers/jpeg.go +++ b/handlers/jpeg.go @@ -9,12 +9,13 @@ import ( // JPEG handler. type JPEG struct { - reader ImageReader + reader ImageReader + quality int } // NewJPEG returns new JPEG handler. -func NewJPEG(reader ImageReader) *JPEG { - return &JPEG{reader} +func NewJPEG(reader ImageReader, quality int) *JPEG { + return &JPEG{reader, quality} } // ServeHTTP handles requests on incoming connections. @@ -36,7 +37,7 @@ func (j *JPEG) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - err = image.NewEncoder(w).Encode(img) + err = image.NewEncoder(w, j.quality).Encode(img) if err != nil { log.Printf("jpeg: encode: %v", err) diff --git a/handlers/mjpeg.go b/handlers/mjpeg.go index 3109293..aa58bdf 100644 --- a/handlers/mjpeg.go +++ b/handlers/mjpeg.go @@ -13,13 +13,14 @@ import ( // MJPEG handler. type MJPEG struct { - reader ImageReader - delay int + reader ImageReader + delay int + quality int } // NewMJPEG returns new MJPEG handler. -func NewMJPEG(reader ImageReader, delay int) *MJPEG { - return &MJPEG{reader, delay} +func NewMJPEG(reader ImageReader, delay, quality int) *MJPEG { + return &MJPEG{reader, delay, quality} } // ServeHTTP handles requests on incoming connections. @@ -61,7 +62,7 @@ loop: continue } - err = image.NewEncoder(partWriter).Encode(img) + err = image.NewEncoder(partWriter, m.quality).Encode(img) if err != nil { log.Printf("mjpeg: encode: %v", err) continue diff --git a/handlers/socket.go b/handlers/socket.go index efcb338..3f3ce03 100644 --- a/handlers/socket.go +++ b/handlers/socket.go @@ -14,13 +14,14 @@ import ( // Socket handler. type Socket struct { - reader ImageReader - delay int + reader ImageReader + delay int + quality int } // NewSocket returns new socket handler. -func NewSocket(reader ImageReader, delay int) *Socket { - return &Socket{reader, delay} +func NewSocket(reader ImageReader, delay, quality int) *Socket { + return &Socket{reader, delay, quality} } // ServeHTTP handles requests on incoming connections. @@ -43,7 +44,7 @@ func (s *Socket) ServeHTTP(w http.ResponseWriter, r *http.Request) { w := new(bytes.Buffer) - err = image.NewEncoder(w).Encode(img) + err = image.NewEncoder(w, s.quality).Encode(img) if err != nil { log.Printf("socket: encode: %v", err) continue diff --git a/image/encode.go b/image/encode.go index c4d462d..209d7cb 100644 --- a/image/encode.go +++ b/image/encode.go @@ -10,18 +10,19 @@ import ( ) // NewEncoder returns a new Encoder. -func NewEncoder(w io.Writer) *Encoder { - return &Encoder{w} +func NewEncoder(w io.Writer, quality int) *Encoder { + return &Encoder{w, quality} } // Encoder struct. type Encoder struct { - w io.Writer + w io.Writer + quality int } // Encode encodes image to JPEG. func (e Encoder) Encode(img image.Image) error { - err := jpeg.Encode(e.w, img, &jpeg.Options{Quality: 75}) + err := jpeg.Encode(e.w, img, &jpeg.Options{Quality: e.quality}) if err != nil { return err } diff --git a/image/encode_jpegli.go b/image/encode_jpegli.go index 38f8492..40075a3 100644 --- a/image/encode_jpegli.go +++ b/image/encode_jpegli.go @@ -11,19 +11,20 @@ import ( ) // NewEncoder returns a new Encoder. -func NewEncoder(w io.Writer) *Encoder { - return &Encoder{w} +func NewEncoder(w io.Writer, quality int) *Encoder { + return &Encoder{w, quality} } // Encoder struct. type Encoder struct { - w io.Writer + w io.Writer + quality int } // Encode encodes image to JPEG. func (e Encoder) Encode(img image.Image) error { return jpegli.Encode(e.w, img, &jpegli.EncodingOptions{ - Quality: 75, + Quality: e.quality, ProgressiveLevel: 0, ChromaSubsampling: image.YCbCrSubsampleRatio420, DCTMethod: jpegli.DCTIFast, diff --git a/image/encode_libjpeg.go b/image/encode_libjpeg.go index 551f8ce..f51313e 100644 --- a/image/encode_libjpeg.go +++ b/image/encode_libjpeg.go @@ -11,19 +11,20 @@ import ( ) // NewEncoder returns a new Encoder. -func NewEncoder(w io.Writer) *Encoder { - return &Encoder{w} +func NewEncoder(w io.Writer, quality int) *Encoder { + return &Encoder{w, quality} } // Encoder struct. type Encoder struct { - w io.Writer + w io.Writer + quality int } // Encode encodes image to JPEG. func (e Encoder) Encode(img image.Image) error { return jpeg.Encode(e.w, img, &jpeg.EncoderOptions{ - Quality: 75, + Quality: e.quality, DCTMethod: jpeg.DCTIFast, ProgressiveMode: false, OptimizeCoding: false, diff --git a/image/image.go b/image/image.go index ca91f73..be15dc8 100644 --- a/image/image.go +++ b/image/image.go @@ -24,11 +24,18 @@ func Rotate(img image.Image, angle int) image.Image { return img } -func Timestamp(img image.Image, format string) (image.Image, error) { - if format == "" { - format = "2006-01-02 15:04:05" +func Flip(img image.Image, dir string) image.Image { + switch dir { + case "horizontal": + img = transform.FlipH(img) + case "vertical": + img = transform.FlipV(img) } + return img +} + +func Timestamp(img image.Image, format string) (image.Image, error) { dimg, ok := img.(draw.Image) if !ok { return img, fmt.Errorf("camera: %T is not a drawable image type", img) diff --git a/server/server.go b/server/server.go index 200d446..94d7ea6 100644 --- a/server/server.go +++ b/server/server.go @@ -16,19 +16,23 @@ type Server struct { Name string Version string - Bind string - Htpasswd string - Index int Delay int - FrameWidth float64 - FrameHeight float64 + Width float64 + Height float64 - Rotate int + Quality int + Rotate int + Flip string - NoWebGL bool - Timestamp bool + NoWebGL bool + + Timestamp bool + TimeFormat string + + Bind string + Htpasswd string Reader handlers.ImageReader } @@ -48,10 +52,10 @@ func (s *Server) ListenAndServe() error { basic = auth.NewBasicAuthenticator(realm, auth.HtpasswdFileProvider(s.Htpasswd)) } - http.Handle("/html", newAuthHandler(handlers.NewHTML(s.FrameWidth, s.FrameHeight, s.NoWebGL), basic)) - http.Handle("/jpeg", newAuthHandler(handlers.NewJPEG(s.Reader), basic)) - http.Handle("/mjpeg", newAuthHandler(handlers.NewMJPEG(s.Reader, s.Delay), basic)) - http.Handle("/socket", newAuthHandler(handlers.NewSocket(s.Reader, s.Delay), basic)) + http.Handle("/html", newAuthHandler(handlers.NewHTML(s.Width, s.Height, s.NoWebGL), basic)) + http.Handle("/jpeg", newAuthHandler(handlers.NewJPEG(s.Reader, s.Quality), basic)) + http.Handle("/mjpeg", newAuthHandler(handlers.NewMJPEG(s.Reader, s.Delay, s.Quality), basic)) + http.Handle("/socket", newAuthHandler(handlers.NewSocket(s.Reader, s.Delay, s.Quality), basic)) http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK)