Merge branch 'opengl'

This commit is contained in:
Martijn Braam 2021-05-03 22:16:51 +02:00
commit d32324208c
55 changed files with 3176 additions and 2135 deletions

View File

@ -1,12 +0,0 @@
.black {
background: #000000;
}
.errorbox {
background: #dd0000;
color: #ffffff;
}
.controlbox {
background: rgba(0,0,0,0.2);
}

View File

@ -1,592 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.37.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkAdjustment" id="control_adj">
<property name="upper">100</property>
<property name="step-increment">1</property>
<property name="page-increment">10</property>
</object>
<object class="GtkWindow" id="window">
<property name="can-focus">False</property>
<property name="title" translatable="yes">Camera</property>
<property name="default-width">360</property>
<property name="default-height">640</property>
<child>
<object class="GtkStack" id="main_stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkBox" id="page_main">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkDrawingArea" id="preview">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<style>
<class name="black"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="control_box">
<property name="can-focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<child>
<object class="GtkLabel" id="control_name">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-end">10</property>
<property name="label" translatable="yes">ISO</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScale" id="control_slider">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="adjustment">control_adj</property>
<property name="round-digits">1</property>
<property name="draw-value">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="control_auto">
<property name="label" translatable="yes">Auto</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="margin-start">10</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<style>
<class name="controlbox"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="controls_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child type="center">
<object class="GtkButton" id="shutter">
<property name="width-request">48</property>
<property name="height-request">48</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="always-show-image">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="resource">/org/postmarketos/Megapixels/shutter-button.svg</property>
</object>
</child>
<style>
<class name="circular"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-top">8</property>
<property name="margin-bottom">8</property>
<property name="spacing">10</property>
<child>
<object class="GtkButton" id="settings">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="resource">/org/postmarketos/Megapixels/settings-symbolic.svg</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="switch_camera">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="resource">/org/postmarketos/Megapixels/switch-camera.svg</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">10</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-top">8</property>
<property name="margin-bottom">8</property>
<property name="spacing">10</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkButton" id="open_directory">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="resource">/org/postmarketos/Megapixels/folder-symbolic.svg</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="open_last">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkStack" id="open_last_stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkImage" id="thumb_last">
<property name="width-request">24</property>
<property name="height-request">24</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkSpinner" id="process_spinner">
<property name="width-request">24</property>
<property name="height-request">24</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">10</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">10</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="error_box">
<property name="can-focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="error_message">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">No error</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="error_close">
<property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
<property name="always-show-image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<style>
<class name="errorbox"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="name">main</property>
<property name="title" translatable="yes">page0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="page_settings">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="shadow-type">none</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkButton" id="settings_back">
<property name="label" translatable="yes">Back</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Settings aren't functional yet</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Photos</property>
<style>
<class name="heading"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="left-padding">12</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Resolution</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Storage mode</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkRadioButton" id="store_vng">
<property name="label" translatable="yes">Debayer with VNG (slowest)</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="store_simple">
<property name="label" translatable="yes">Debayer with linear interpolation</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">store_vng</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="store_raw">
<property name="label" translatable="yes">Raw</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">store_vng</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
<style>
<class name="view"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">settings</property>
<property name="title" translatable="yes">page1</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -11,7 +11,7 @@ capture-rate=10
capture-fmt=BGGR8
preview-width=1280
preview-height=720
preview-rate=20
preview-rate=30
preview-fmt=BGGR8
rotate=270
colormatrix=1.384,-0.3203,-0.0124,-0.2728,1.049,0.1556,-0.0506,0.2577,0.8050
@ -29,11 +29,11 @@ driver=gc2145
media-driver=sun6i-csi
capture-width=1280
capture-height=960
capture-rate=30
capture-rate=60
capture-fmt=BGGR8
preview-width=1280
preview-height=960
preview-rate=30
preview-rate=60
preview-fmt=BGGR8
rotate=90
mirrored=true

View File

@ -11,7 +11,7 @@ capture-rate=10
capture-fmt=BGGR8
preview-width=1280
preview-height=720
preview-rate=20
preview-rate=30
preview-fmt=BGGR8
rotate=270
colormatrix=1.384,-0.3203,-0.0124,-0.2728,1.049,0.1556,-0.0506,0.2577,0.8050
@ -29,11 +29,11 @@ driver=gc2145
media-driver=sun6i-csi
capture-width=1280
capture-height=960
capture-rate=30
capture-rate=60
capture-fmt=BGGR8
preview-width=1280
preview-height=960
preview-rate=30
preview-rate=60
preview-fmt=BGGR8
rotate=90
mirrored=true

View File

@ -11,7 +11,7 @@ capture-rate=10
capture-fmt=BGGR8
preview-width=1280
preview-height=720
preview-rate=20
preview-rate=30
preview-fmt=BGGR8
rotate=270
mirrored=false
@ -30,11 +30,11 @@ driver=gc2145
media-driver=sun6i-csi
capture-width=1280
capture-height=960
capture-rate=30
capture-rate=60
capture-fmt=BGGR8
preview-width=1280
preview-height=960
preview-rate=30
preview-rate=60
preview-fmt=BGGR8
rotate=90
mirrored=true

11
data/blit.frag Normal file
View File

@ -0,0 +1,11 @@
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D texture;
varying vec2 uv;
void main() {
gl_FragColor = vec4(texture2D(texture, uv).rgb, 1);
}

16
data/blit.vert Normal file
View File

@ -0,0 +1,16 @@
#ifdef GL_ES
precision mediump float;
#endif
attribute vec2 vert;
attribute vec2 tex_coord;
uniform mat3 transform;
varying vec2 uv;
void main() {
uv = tex_coord;
gl_Position = vec4(transform * vec3(vert, 1), 1);
}

13
data/camera.css Normal file
View File

@ -0,0 +1,13 @@
.errorbox {
background: #dd0000;
color: #ffffff;
}
.controlbox {
background: rgba(0, 0, 0, 0.2);
}
.button-overlay {
opacity: 0.9;
background-color: rgba(0, 0, 0, 0.2);
}

250
data/camera.ui Normal file
View File

@ -0,0 +1,250 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<object class="GtkWindow" id="window">
<property name="can-focus">0</property>
<property name="default-width">360</property>
<property name="default-height">640</property>
<property name="decorated">0</property>
<property name="child">
<object class="GtkStack" id="main_stack">
<property name="can-focus">0</property>
<child>
<object class="GtkStackPage">
<property name="name">main</property>
<property name="child">
<object class="GtkOverlay">
<property name="can-focus">0</property>
<child>
<object class="GtkGLArea" id="preview">
<property name="vexpand">1</property>
<property name="can-focus">0</property>
<property name="use-es">1</property>
</object>
</child>
<child type="overlay">
<object class="GtkBox" id="top-box">
<property name="orientation">vertical</property>
<property name="halign">fill</property>
<property name="valign">start</property>
<property name="spacing">5</property>
<property name="can-focus">0</property>
<child>
<object class="GtkBox">
<property name="orientation" bind-source="bottom-box" bind-property="orientation"/>
<property name="vexpand">1</property>
<property name="hexpand">1</property>
<property name="valign">start</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="can-focus">0</property>
<property name="spacing">5</property>
<child>
<object class="GtkButton" id="iso-controls-button">
<property name="valign">start</property>
<property name="label">ISO</property>
<style>
<class name="circular"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="shutter-controls-button">
<property name="valign">start</property>
<property name="icon-name">shutter-symbolic</property>
<style>
<class name="circular"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton">
<property name="visible">0</property>
<property name="halign">start</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<child>
<object class="GtkBox">
<property name="orientation">horizontal</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel">
<property name="label">Error</property>
</object>
</child>
<child>
<object class="GtkImage">
<property name="icon-name">dialog-warning-symbolic</property>
</object>
</child>
</object>
</child>
<style>
<class name="destructive-action"/>
</style>
</object>
</child>
<style>
<class name="button-overlay"/>
</style>
</object>
</child>
<child type="overlay">
<object class="GtkBox" id="bottom-box">
<property name="orientation">horizontal</property>
<property name="halign">fill</property>
<property name="valign">end</property>
<child>
<object class="GtkBox">
<property name="orientation" bind-source="bottom-box" bind-property="orientation"/>
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkButton">
<property name="action-name">app.open-settings</property>
<property name="icon-name">settings-symbolic</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="action-name">app.switch-camera</property>
<property name="icon-name">switch-camera-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton">
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="action-name">app.capture</property>
<child>
<object class="GtkImage">
<property name="pixel-size">60</property>
<property name="can-focus">0</property>
<property name="icon-name">shutter-button-symbolic</property>
</object>
</child>
<style>
<class name="circular"/>
</style>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation" bind-source="bottom-box" bind-property="orientation"/>
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkButton">
<property name="action-name">app.open-last</property>
<child>
<object class="GtkStack" id="open_last_stack">
<property name="can-focus">0</property>
<child>
<object class="GtkImage" id="thumb_last">
<property name="width-request">24</property>
<property name="height-request">24</property>
<property name="can-focus">0</property>
</object>
</child>
<child>
<object class="GtkSpinner" id="process_spinner">
<property name="width-request">24</property>
<property name="height-request">24</property>
<property name="can-focus">0</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton">
<property name="action-name">app.open-photos</property>
<property name="icon-name">folder-symbolic</property>
</object>
</child>
</object>
</child>
<style>
<class name="button-overlay"/>
</style>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">settings</property>
<property name="title" translatable="yes">page1</property>
<property name="child">
<object class="GtkScrolledWindow">
<property name="child">
<object class="GtkViewport">
<property name="can-focus">0</property>
<property name="child">
<object class="GtkBox">
<property name="can-focus">0</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkBox">
<property name="can-focus">0</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Back</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="action-name">app.close-settings</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="can-focus">0</property>
<property name="label" translatable="yes">Settings aren't functional yet</property>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
</object>
</property>
</object>
</interface>

42
data/controls-popover.ui Normal file
View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<object class="GtkPopover" id="controls">
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="hexpand">1</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="title">
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">horizontal</property>
<property name="hexpand">1</property>
<property name="spacing">5</property>
<child>
<object class="GtkScale" id="scale">
<property name="orientation">horizontal</property>
<property name="draw-value">0</property>
<property name="width-request">150</property>
</object>
</child>
<child>
<object class="GtkLabel" id="value-label">
</object>
</child>
<child>
<object class="GtkToggleButton" id="auto-button">
<property name="valign">center</property>
<property name="label">auto</property>
<property name="margin-start">5</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

34
data/debayer.frag Normal file
View File

@ -0,0 +1,34 @@
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D texture;
uniform mat3 color_matrix;
varying vec2 top_left_uv;
varying vec2 top_right_uv;
varying vec2 bottom_left_uv;
varying vec2 bottom_right_uv;
void main() {
// Note the coordinates for texture samples need to be a varying, as the
// Mali-400 has this as a fast path allowing 32-bit floats. Otherwise
// they end up as 16-bit floats and that's not accurate enough.
vec4 samples = vec4(
texture2D(texture, top_left_uv).r,
texture2D(texture, top_right_uv).r,
texture2D(texture, bottom_left_uv).r,
texture2D(texture, bottom_right_uv).r);
// Assume BGGR for now. Currently this just takes 3 of the four samples
// for each pixel, there's room here to do some better debayering.
vec3 color = vec3(samples.w, (samples.y + samples.w) / 2.0, samples.x);
// Fast SRGB estimate. See https://mimosa-pudica.net/fast-gamma/
vec3 srgb_color = (vec3(1.138) * inversesqrt(color) - vec3(0.138)) * color;
// Slow SRGB estimate
// vec3 srgb_color = pow(color, vec3(1.0 / 2.2));
gl_FragColor = vec4(color_matrix * srgb_color, 1);
}

23
data/debayer.vert Normal file
View File

@ -0,0 +1,23 @@
#ifdef GL_ES
precision mediump float;
#endif
attribute vec2 vert;
attribute vec2 tex_coord;
uniform mat3 transform;
uniform vec2 pixel_size;
varying vec2 top_left_uv;
varying vec2 top_right_uv;
varying vec2 bottom_left_uv;
varying vec2 bottom_right_uv;
void main() {
top_left_uv = tex_coord - pixel_size / 2.0;
bottom_right_uv = tex_coord + pixel_size / 2.0;
top_right_uv = vec2(top_left_uv.x, bottom_right_uv.y);
bottom_left_uv = vec2(bottom_right_uv.x, top_left_uv.y);
gl_Position = vec4(transform * vec3(vert, 1), 1);
}

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

15
data/meson.build Normal file
View File

@ -0,0 +1,15 @@
resources = gnome.compile_resources('megapixels-resources',
'org.postmarketos.Megapixels.gresource.xml')
install_data(['org.postmarketos.Megapixels.desktop'],
install_dir: get_option('datadir') / 'applications')
install_data(['org.postmarketos.Megapixels.metainfo.xml'],
install_dir: get_option('datadir') / 'metainfo')
install_data('org.postmarketos.Megapixels.svg',
install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps'))
install_data(['postprocess.sh'],
install_dir: get_option('datadir') / 'megapixels/',
install_mode: 'rwxr-xr-x')

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/postmarketos/Megapixels">
<file preprocess="xml-stripblanks">camera.ui</file>
<file preprocess="xml-stripblanks">controls-popover.ui</file>
<file>camera.css</file>
<file preprocess="xml-stripblanks">switch-camera-symbolic.svg</file>
<file preprocess="xml-stripblanks">shutter-button-symbolic.svg</file>
<file preprocess="xml-stripblanks">folder-symbolic.svg</file>
<file preprocess="xml-stripblanks">settings-symbolic.svg</file>
<file preprocess="xml-stripblanks">shutter-symbolic.svg</file>
<file>blit.vert</file>
<file>blit.frag</file>
<file>solid.vert</file>
<file>solid.frag</file>
<file>debayer.vert</file>
<file>debayer.frag</file>
</gresource>
</gresources>

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

32
data/shutter-symbolic.svg Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 8.4666665 8.466667"
version="1.1"
id="svg8">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:#bebebe;fill-opacity:1;stroke-width:1"
d="M 16 0 A 16.000001 16.000001 0 0 0 8.5097656 1.8730469 L 11.611328 13.443359 L 19.189453 0.3203125 A 16.000001 16.000001 0 0 0 16 0 z M 20.699219 0.70703125 L 14.705078 11.087891 L 29.330078 7.1679688 A 16.000001 16.000001 0 0 0 20.699219 0.70703125 z M 7.1679688 2.6699219 A 16.000001 16.000001 0 0 0 0.70703125 11.300781 L 11.087891 17.294922 L 7.1679688 2.6699219 z M 30.126953 8.5117188 L 18.558594 11.611328 L 31.679688 19.189453 A 16.000001 16.000001 0 0 0 32 16 A 16.000001 16.000001 0 0 0 30.126953 8.5117188 z M 0.3203125 12.810547 A 16.000001 16.000001 0 0 0 0 16 A 16.000001 16.000001 0 0 0 1.8730469 23.490234 L 13.443359 20.388672 L 0.3203125 12.810547 z M 20.912109 14.705078 L 24.832031 29.330078 A 16.000001 16.000001 0 0 0 31.292969 20.699219 L 20.912109 14.705078 z M 20.388672 18.558594 L 12.8125 31.679688 A 16.000001 16.000001 0 0 0 16 32 A 16.000001 16.000001 0 0 0 23.490234 30.126953 L 20.388672 18.558594 z M 17.294922 20.912109 L 2.6699219 24.832031 A 16.000001 16.000001 0 0 0 11.300781 31.292969 L 17.294922 20.912109 z "
transform="scale(0.26458334)"
id="path4537" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

9
data/solid.frag Normal file
View File

@ -0,0 +1,9 @@
#ifdef GL_ES
precision mediump float;
#endif
uniform vec4 color;
void main() {
gl_FragColor = color;
}

9
data/solid.vert Normal file
View File

@ -0,0 +1,9 @@
#ifdef GL_ES
precision mediump float;
#endif
attribute vec2 vert;
void main() {
gl_Position = vec4(vert, 0, 1);
}

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

792
main.c
View File

@ -1,792 +0,0 @@
#include "main.h"
#include <errno.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <linux/media.h>
#include <linux/v4l2-subdev.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#include <assert.h>
#include <limits.h>
#include <linux/kdev_t.h>
#include <sys/sysmacros.h>
#include <asm/errno.h>
#include <wordexp.h>
#include <gtk/gtk.h>
#include <locale.h>
#include <zbar.h>
#include "camera_config.h"
#include "quickpreview.h"
#include "io_pipeline.h"
enum user_control { USER_CONTROL_ISO, USER_CONTROL_SHUTTER };
static bool camera_is_initialized = false;
static const struct mp_camera_config *camera = NULL;
static MPCameraMode mode;
static int preview_width = -1;
static int preview_height = -1;
static bool gain_is_manual = false;
static int gain;
static int gain_max;
static bool exposure_is_manual = false;
static int exposure;
static bool has_auto_focus_continuous;
static bool has_auto_focus_start;
static cairo_surface_t *surface = NULL;
static cairo_surface_t *status_surface = NULL;
static char last_path[260] = "";
static MPZBarScanResult *zbar_result = NULL;
static int burst_length = 3;
static enum user_control current_control;
// Widgets
GtkWidget *preview;
GtkWidget *error_box;
GtkWidget *error_message;
GtkWidget *main_stack;
GtkWidget *open_last_stack;
GtkWidget *thumb_last;
GtkWidget *process_spinner;
GtkWidget *control_box;
GtkWidget *control_name;
GtkAdjustment *control_slider;
GtkWidget *control_auto;
int
remap(int value, int input_min, int input_max, int output_min, int output_max)
{
const long long factor = 1000000000;
long long output_spread = output_max - output_min;
long long input_spread = input_max - input_min;
long long zero_value = value - input_min;
zero_value *= factor;
long long percentage = zero_value / input_spread;
long long zero_output = percentage * output_spread / factor;
long long result = output_min + zero_output;
return (int)result;
}
static void
update_io_pipeline()
{
struct mp_io_pipeline_state io_state = {
.camera = camera,
.burst_length = burst_length,
.preview_width = preview_width,
.preview_height = preview_height,
.gain_is_manual = gain_is_manual,
.gain = gain,
.exposure_is_manual = exposure_is_manual,
.exposure = exposure,
};
mp_io_pipeline_update_state(&io_state);
}
static bool
update_state(const struct mp_main_state *state)
{
if (!camera_is_initialized) {
camera_is_initialized = true;
}
if (camera == state->camera) {
mode = state->mode;
if (!gain_is_manual) {
gain = state->gain;
}
gain_max = state->gain_max;
if (!exposure_is_manual) {
exposure = state->exposure;
}
has_auto_focus_continuous = state->has_auto_focus_continuous;
has_auto_focus_start = state->has_auto_focus_start;
}
return false;
}
void
mp_main_update_state(const struct mp_main_state *state)
{
struct mp_main_state *state_copy = malloc(sizeof(struct mp_main_state));
*state_copy = *state;
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
(GSourceFunc)update_state, state_copy, free);
}
static bool set_zbar_result(MPZBarScanResult *result)
{
if (zbar_result) {
for (uint8_t i = 0; i < zbar_result->size; ++i) {
free(zbar_result->codes[i].data);
}
free(zbar_result);
}
zbar_result = result;
gtk_widget_queue_draw(preview);
return false;
}
void mp_main_set_zbar_result(MPZBarScanResult *result)
{
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
(GSourceFunc)set_zbar_result, result, NULL);
}
static bool
set_preview(cairo_surface_t *image)
{
if (surface) {
cairo_surface_destroy(surface);
}
surface = image;
gtk_widget_queue_draw(preview);
return false;
}
void
mp_main_set_preview(cairo_surface_t *image)
{
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
(GSourceFunc)set_preview, image, NULL);
}
static void transform_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height,
int src_width, int src_height)
{
cairo_translate(cr, dst_width / 2, dst_height / 2);
double scale = MIN(dst_width / (double)src_width, dst_height / (double)src_height);
cairo_scale(cr, scale, scale);
cairo_translate(cr, -src_width / 2, -src_height / 2);
}
void
draw_surface_scaled_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height,
cairo_surface_t *surface)
{
cairo_save(cr);
int width = cairo_image_surface_get_width(surface);
int height = cairo_image_surface_get_height(surface);
transform_centered(cr, dst_width, dst_height, width, height);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr);
cairo_restore(cr);
}
struct capture_completed_args {
cairo_surface_t *thumb;
char *fname;
};
static bool
capture_completed(struct capture_completed_args *args)
{
strncpy(last_path, args->fname, 259);
gtk_image_set_from_surface(GTK_IMAGE(thumb_last), args->thumb);
gtk_spinner_stop(GTK_SPINNER(process_spinner));
gtk_stack_set_visible_child(GTK_STACK(open_last_stack), thumb_last);
cairo_surface_destroy(args->thumb);
g_free(args->fname);
return false;
}
void
mp_main_capture_completed(cairo_surface_t *thumb, const char *fname)
{
struct capture_completed_args *args = malloc(sizeof(struct capture_completed_args));
args->thumb = thumb;
args->fname = g_strdup(fname);
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
(GSourceFunc)capture_completed, args, free);
}
static void
draw_controls()
{
cairo_t *cr;
char iso[6];
int temp;
char shutterangle[6];
if (exposure_is_manual) {
temp = (int)((float)exposure / (float)camera->capture_mode.height *
360);
sprintf(shutterangle, "%d\u00b0", temp);
} else {
sprintf(shutterangle, "auto");
}
if (gain_is_manual) {
temp = remap(gain - 1, 0, gain_max, camera->iso_min,
camera->iso_max);
sprintf(iso, "%d", temp);
} else {
sprintf(iso, "auto");
}
if (status_surface)
cairo_surface_destroy(status_surface);
// Make a service to show status of controls, 32px high
if (gtk_widget_get_window(preview) == NULL) {
return;
}
status_surface =
gdk_window_create_similar_surface(gtk_widget_get_window(preview),
CAIRO_CONTENT_COLOR_ALPHA,
preview_width, 32);
cr = cairo_create(status_surface);
cairo_set_source_rgba(cr, 0, 0, 0, 0.0);
cairo_paint(cr);
// Draw the outlines for the headings
cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 9);
cairo_set_source_rgba(cr, 0, 0, 0, 1);
cairo_move_to(cr, 16, 16);
cairo_text_path(cr, "ISO");
cairo_stroke(cr);
cairo_move_to(cr, 60, 16);
cairo_text_path(cr, "Shutter");
cairo_stroke(cr);
// Draw the fill for the headings
cairo_set_source_rgba(cr, 1, 1, 1, 1);
cairo_move_to(cr, 16, 16);
cairo_show_text(cr, "ISO");
cairo_move_to(cr, 60, 16);
cairo_show_text(cr, "Shutter");
// Draw the outlines for the values
cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(cr, 11);
cairo_set_source_rgba(cr, 0, 0, 0, 1);
cairo_move_to(cr, 16, 26);
cairo_text_path(cr, iso);
cairo_stroke(cr);
cairo_move_to(cr, 60, 26);
cairo_text_path(cr, shutterangle);
cairo_stroke(cr);
// Draw the fill for the values
cairo_set_source_rgba(cr, 1, 1, 1, 1);
cairo_move_to(cr, 16, 26);
cairo_show_text(cr, iso);
cairo_move_to(cr, 60, 26);
cairo_show_text(cr, shutterangle);
cairo_destroy(cr);
gtk_widget_queue_draw_area(preview, 0, 0, preview_width, 32);
}
static gboolean
preview_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
{
if (!camera_is_initialized) {
return FALSE;
}
// Clear preview area with black
cairo_paint(cr);
if (surface) {
// Draw camera preview
cairo_save(cr);
int width = cairo_image_surface_get_width(surface);
int height = cairo_image_surface_get_height(surface);
transform_centered(cr, preview_width, preview_height, width, height);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr);
// Draw zbar image
if (zbar_result) {
for (uint8_t i = 0; i < zbar_result->size; ++i) {
MPZBarCode *code = &zbar_result->codes[i];
cairo_set_line_width(cr, 3.0);
cairo_set_source_rgba(cr, 0, 0.5, 1, 0.75);
cairo_new_path(cr);
cairo_move_to(cr, code->bounds_x[0], code->bounds_y[0]);
for (uint8_t i = 0; i < 4; ++i) {
cairo_line_to(cr, code->bounds_x[i], code->bounds_y[i]);
}
cairo_close_path(cr);
cairo_stroke(cr);
cairo_save(cr);
cairo_translate(cr, code->bounds_x[0], code->bounds_y[0]);
cairo_show_text(cr, code->data);
cairo_restore(cr);
}
}
cairo_restore(cr);
}
// Draw control overlay
cairo_set_source_surface(cr, status_surface, 0, 0);
cairo_paint(cr);
return FALSE;
}
static gboolean
preview_configure(GtkWidget *widget, GdkEventConfigure *event)
{
int new_preview_width = gtk_widget_get_allocated_width(widget);
int new_preview_height = gtk_widget_get_allocated_height(widget);
if (preview_width != new_preview_width ||
preview_height != new_preview_height) {
preview_width = new_preview_width;
preview_height = new_preview_height;
update_io_pipeline();
}
draw_controls();
return TRUE;
}
void
on_open_last_clicked(GtkWidget *widget, gpointer user_data)
{
char uri[275];
GError *error = NULL;
if (strlen(last_path) == 0) {
return;
}
sprintf(uri, "file://%s", last_path);
if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
g_printerr("Could not launch image viewer for '%s': %s\n", uri, error->message);
}
}
void
on_open_directory_clicked(GtkWidget *widget, gpointer user_data)
{
char uri[270];
GError *error = NULL;
sprintf(uri, "file://%s", g_get_user_special_dir(G_USER_DIRECTORY_PICTURES));
if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
g_printerr("Could not launch image viewer: %s\n", error->message);
}
}
void
on_shutter_clicked(GtkWidget *widget, gpointer user_data)
{
gtk_spinner_start(GTK_SPINNER(process_spinner));
gtk_stack_set_visible_child(GTK_STACK(open_last_stack), process_spinner);
mp_io_pipeline_capture();
}
void
on_capture_shortcut(void)
{
on_shutter_clicked(NULL, NULL);
}
static bool
check_point_inside_bounds(int x, int y, int *bounds_x, int *bounds_y)
{
bool right = false, left = false, top = false, bottom = false;
for (int i = 0; i < 4; ++i) {
if (x <= bounds_x[i])
left = true;
if (x >= bounds_x[i])
right = true;
if (y <= bounds_y[i])
top = true;
if (y >= bounds_y[i])
bottom = true;
}
return right && left && top && bottom;
}
static void
on_zbar_code_tapped(GtkWidget *widget, const MPZBarCode *code)
{
GtkWidget *dialog;
GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
bool data_is_url = g_uri_is_valid(
code->data, G_URI_FLAGS_PARSE_RELAXED, NULL);
char* data = strdup(code->data);
if (data_is_url) {
dialog = gtk_message_dialog_new(
GTK_WINDOW(gtk_widget_get_toplevel(widget)),
flags,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_NONE,
"Found a URL '%s' encoded in a %s code.",
code->data,
code->type);
gtk_dialog_add_buttons(
GTK_DIALOG(dialog),
"_Open URL",
GTK_RESPONSE_YES,
NULL);
} else {
dialog = gtk_message_dialog_new(
GTK_WINDOW(gtk_widget_get_toplevel(widget)),
flags,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_NONE,
"Found '%s' encoded in a %s code.",
code->data,
code->type);
}
gtk_dialog_add_buttons(
GTK_DIALOG(dialog),
"_Copy",
GTK_RESPONSE_ACCEPT,
"_Cancel",
GTK_RESPONSE_CANCEL,
NULL);
int result = gtk_dialog_run(GTK_DIALOG(dialog));
GError *error = NULL;
switch (result) {
case GTK_RESPONSE_YES:
if (!g_app_info_launch_default_for_uri(data,
NULL, &error)) {
g_printerr("Could not launch application: %s\n",
error->message);
}
case GTK_RESPONSE_ACCEPT:
gtk_clipboard_set_text(
gtk_clipboard_get(GDK_SELECTION_PRIMARY),
data, -1);
case GTK_RESPONSE_CANCEL:
break;
default:
g_printerr("Wrong dialog result: %d\n", result);
}
gtk_widget_destroy(dialog);
}
void
on_preview_tap(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
{
if (event->type != GDK_BUTTON_PRESS)
return;
// Handle taps on the controls
if (event->y < 32) {
if (gtk_widget_is_visible(control_box)) {
gtk_widget_hide(control_box);
return;
} else {
gtk_widget_show(control_box);
}
if (event->x < 60) {
// ISO
current_control = USER_CONTROL_ISO;
gtk_label_set_text(GTK_LABEL(control_name), "ISO");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto),
!gain_is_manual);
gtk_adjustment_set_lower(control_slider, 0.0);
gtk_adjustment_set_upper(control_slider, (float)gain_max);
gtk_adjustment_set_value(control_slider, (double)gain);
} else if (event->x > 60 && event->x < 120) {
// Shutter angle
current_control = USER_CONTROL_SHUTTER;
gtk_label_set_text(GTK_LABEL(control_name), "Shutter");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto),
!exposure_is_manual);
gtk_adjustment_set_lower(control_slider, 1.0);
gtk_adjustment_set_upper(control_slider, 360.0);
gtk_adjustment_set_value(control_slider, (double)exposure);
}
return;
}
// Tapped zbar result
if (zbar_result) {
// Transform the event coordinates to the image
int width = cairo_image_surface_get_width(surface);
int height = cairo_image_surface_get_height(surface);
double scale = MIN(preview_width / (double)width, preview_height / (double)height);
int x = (event->x - preview_width / 2) / scale + width / 2;
int y = (event->y - preview_height / 2) / scale + height / 2;
for (uint8_t i = 0; i < zbar_result->size; ++i) {
MPZBarCode *code = &zbar_result->codes[i];
if (check_point_inside_bounds(x, y, code->bounds_x, code->bounds_y)) {
on_zbar_code_tapped(widget, code);
return;
}
}
}
// Tapped preview image itself, try focussing
if (has_auto_focus_start) {
mp_io_pipeline_focus();
}
}
void
on_error_close_clicked(GtkWidget *widget, gpointer user_data)
{
gtk_widget_hide(error_box);
}
void
on_camera_switch_clicked(GtkWidget *widget, gpointer user_data)
{
size_t next_index = camera->index + 1;
const struct mp_camera_config *next_camera =
mp_get_camera_config(next_index);
if (!next_camera) {
next_index = 0;
next_camera = mp_get_camera_config(next_index);
}
camera = next_camera;
update_io_pipeline();
}
void
on_settings_btn_clicked(GtkWidget *widget, gpointer user_data)
{
gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "settings");
}
void
on_back_clicked(GtkWidget *widget, gpointer user_data)
{
gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "main");
}
void
on_control_auto_toggled(GtkToggleButton *widget, gpointer user_data)
{
bool is_manual = gtk_toggle_button_get_active(widget) ? false : true;
bool has_changed;
switch (current_control) {
case USER_CONTROL_ISO:
if (gain_is_manual != is_manual) {
gain_is_manual = is_manual;
has_changed = true;
}
break;
case USER_CONTROL_SHUTTER:
if (exposure_is_manual != is_manual) {
exposure_is_manual = is_manual;
has_changed = true;
}
break;
}
if (has_changed) {
// The slider might have been moved while Auto mode is active. When entering
// Manual mode, first read the slider value to sync with those changes.
double value = gtk_adjustment_get_value(control_slider);
switch (current_control) {
case USER_CONTROL_ISO:
if (value != gain) {
gain = (int)value;
}
break;
case USER_CONTROL_SHUTTER: {
// So far all sensors use exposure time in number of sensor rows
int new_exposure =
(int)(value / 360.0 * camera->capture_mode.height);
if (new_exposure != exposure) {
exposure = new_exposure;
}
break;
}
}
update_io_pipeline();
draw_controls();
}
}
void
on_control_slider_changed(GtkAdjustment *widget, gpointer user_data)
{
double value = gtk_adjustment_get_value(widget);
bool has_changed = false;
switch (current_control) {
case USER_CONTROL_ISO:
if (value != gain) {
gain = (int)value;
has_changed = true;
}
break;
case USER_CONTROL_SHUTTER: {
// So far all sensors use exposure time in number of sensor rows
int new_exposure =
(int)(value / 360.0 * camera->capture_mode.height);
if (new_exposure != exposure) {
exposure = new_exposure;
has_changed = true;
}
break;
}
}
if (has_changed) {
update_io_pipeline();
draw_controls();
}
}
int
main(int argc, char *argv[])
{
if (!mp_load_config())
return 1;
setenv("LC_NUMERIC", "C", 1);
gtk_init(&argc, &argv);
g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme",
TRUE, NULL);
GtkBuilder *builder = gtk_builder_new_from_resource(
"/org/postmarketos/Megapixels/camera.glade");
GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
GtkWidget *shutter = GTK_WIDGET(gtk_builder_get_object(builder, "shutter"));
GtkWidget *switch_btn =
GTK_WIDGET(gtk_builder_get_object(builder, "switch_camera"));
GtkWidget *settings_btn =
GTK_WIDGET(gtk_builder_get_object(builder, "settings"));
GtkWidget *settings_back =
GTK_WIDGET(gtk_builder_get_object(builder, "settings_back"));
GtkWidget *error_close =
GTK_WIDGET(gtk_builder_get_object(builder, "error_close"));
GtkWidget *open_last =
GTK_WIDGET(gtk_builder_get_object(builder, "open_last"));
GtkWidget *open_directory =
GTK_WIDGET(gtk_builder_get_object(builder, "open_directory"));
preview = GTK_WIDGET(gtk_builder_get_object(builder, "preview"));
error_box = GTK_WIDGET(gtk_builder_get_object(builder, "error_box"));
error_message = GTK_WIDGET(gtk_builder_get_object(builder, "error_message"));
main_stack = GTK_WIDGET(gtk_builder_get_object(builder, "main_stack"));
open_last_stack = GTK_WIDGET(gtk_builder_get_object(builder, "open_last_stack"));
thumb_last = GTK_WIDGET(gtk_builder_get_object(builder, "thumb_last"));
process_spinner = GTK_WIDGET(gtk_builder_get_object(builder, "process_spinner"));
control_box = GTK_WIDGET(gtk_builder_get_object(builder, "control_box"));
control_name = GTK_WIDGET(gtk_builder_get_object(builder, "control_name"));
control_slider =
GTK_ADJUSTMENT(gtk_builder_get_object(builder, "control_adj"));
control_auto = GTK_WIDGET(gtk_builder_get_object(builder, "control_auto"));
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(shutter, "clicked", G_CALLBACK(on_shutter_clicked), NULL);
g_signal_connect(error_close, "clicked", G_CALLBACK(on_error_close_clicked),
NULL);
g_signal_connect(switch_btn, "clicked", G_CALLBACK(on_camera_switch_clicked),
NULL);
g_signal_connect(settings_btn, "clicked",
G_CALLBACK(on_settings_btn_clicked), NULL);
g_signal_connect(settings_back, "clicked", G_CALLBACK(on_back_clicked),
NULL);
g_signal_connect(open_last, "clicked", G_CALLBACK(on_open_last_clicked),
NULL);
g_signal_connect(open_directory, "clicked",
G_CALLBACK(on_open_directory_clicked), NULL);
g_signal_connect(preview, "draw", G_CALLBACK(preview_draw), NULL);
g_signal_connect(preview, "configure-event", G_CALLBACK(preview_configure),
NULL);
gtk_widget_set_events(preview, gtk_widget_get_events(preview) |
GDK_BUTTON_PRESS_MASK |
GDK_POINTER_MOTION_MASK);
g_signal_connect(preview, "button-press-event", G_CALLBACK(on_preview_tap),
NULL);
g_signal_connect(control_auto, "toggled",
G_CALLBACK(on_control_auto_toggled), NULL);
g_signal_connect(control_slider, "value-changed",
G_CALLBACK(on_control_slider_changed), NULL);
GtkCssProvider *provider = gtk_css_provider_new();
if (access("camera.css", F_OK) != -1) {
gtk_css_provider_load_from_path(provider, "camera.css", NULL);
} else {
gtk_css_provider_load_from_resource(
provider, "/org/postmarketos/Megapixels/camera.css");
}
GtkStyleContext *context = gtk_widget_get_style_context(error_box);
gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_USER);
context = gtk_widget_get_style_context(control_box);
gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_USER);
GClosure* capture_shortcut = g_cclosure_new(on_capture_shortcut, 0, 0);
GtkAccelGroup* accel_group = gtk_accel_group_new();
gtk_accel_group_connect(accel_group,
GDK_KEY_space,
0,
0,
capture_shortcut);
gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
mp_io_pipeline_start();
camera = mp_get_camera_config(0);
update_io_pipeline();
gtk_widget_show(window);
gtk_main();
mp_io_pipeline_stop();
return 0;
}

View File

@ -1,73 +1,76 @@
project('megapixels', 'c')
gnome = import('gnome')
gtkdep = dependency('gtk+-3.0')
gtkdep = dependency('gtk4')
tiff = dependency('libtiff-4')
zbar = dependency('zbar')
threads = dependency('threads')
# gl = dependency('gl')
epoxy = dependency('epoxy')
cc = meson.get_compiler('c')
libm = cc.find_library('m', required: false)
resources = gnome.compile_resources('megapixels-resources', 'org.postmarketos.Megapixels.gresource.xml')
subdir('data')
conf = configuration_data()
conf.set_quoted('DATADIR', join_paths(get_option('prefix'), get_option('datadir')))
conf.set_quoted('SYSCONFDIR', get_option('sysconfdir'))
configure_file(
output: 'config.h',
configuration: conf )
configuration: conf)
# Define DEBUG for debug builds only (debugoptimized is not included on this one)
if get_option('buildtype') == 'debug'
add_global_arguments('-DDEBUG', language: 'c')
endif
# Workaround for libtiff having ABI changes but not changing the internal version number
# Workaround for libtiff having ABI changes but not changing the internal
# version number
if get_option('tiffcfapattern')
add_global_arguments('-DLIBTIFF_CFA_PATTERN', language: 'c')
endif
executable('megapixels',
'main.c',
'ini.c',
'quickpreview.c',
'camera.c',
'device.c',
'pipeline.c',
'camera_config.c',
'io_pipeline.c',
'process_pipeline.c',
'zbar_pipeline.c',
'matrix.c',
resources,
dependencies : [gtkdep, libm, tiff, zbar, threads],
install : true)
'src/main.c',
'src/ini.c',
'src/gles2_debayer.c',
'src/gl_util.c',
'src/camera.c',
'src/device.c',
'src/pipeline.c',
'src/camera_config.c',
'src/io_pipeline.c',
'src/process_pipeline.c',
'src/zbar_pipeline.c',
'src/matrix.c',
resources,
include_directories: 'src/',
dependencies: [gtkdep, libm, tiff, zbar, threads, epoxy],
install: true,
link_args: '-Wl,-ldl')
install_data(['data/org.postmarketos.Megapixels.desktop'],
install_dir : get_option('datadir') / 'applications')
install_data(['data/org.postmarketos.Megapixels.metainfo.xml'],
install_dir : get_option('datadir') / 'metainfo')
install_data('data/org.postmarketos.Megapixels.svg',
install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps')
)
install_data([
'config/pine64,pinephone-1.0.ini',
'config/pine64,pinephone-1.1.ini',
'config/pine64,pinephone-1.2.ini',
'config/pine64,pinetab.ini',
install_data(
[
'config/pine64,pinephone-1.0.ini',
'config/pine64,pinephone-1.1.ini',
'config/pine64,pinephone-1.2.ini',
'config/pine64,pinetab.ini',
],
install_dir : get_option('datadir') / 'megapixels/config/')
install_data(['postprocess.sh'],
install_dir : get_option('datadir') / 'megapixels/',
install_mode: 'rwxr-xr-x')
install_dir: get_option('datadir') / 'megapixels/config/')
# Tools
executable('megapixels-list-devices', 'tools/list_devices.c', 'device.c', dependencies: [gtkdep], install: true)
executable('megapixels-camera-test', 'tools/camera_test.c', 'camera.c', 'device.c', dependencies: [gtkdep], install: true)
executable('megapixels-list-devices',
'tools/list_devices.c',
'src/device.c',
include_directories: 'src/',
dependencies: [gtkdep],
install: true)
test_quickpreview = executable('test_quickpreview', 'tests/test_quickpreview.c', 'quickpreview.c', 'camera.c', dependencies: [gtkdep])
test('quickpreview', test_quickpreview)
executable('megapixels-camera-test',
'tools/camera_test.c',
'src/camera.c',
'src/device.c',
include_directories: 'src/',
dependencies: [gtkdep],
install: true)

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/postmarketos/Megapixels">
<file>camera.glade</file>
<file>camera.css</file>
<file>switch-camera.svg</file>
<file>shutter-button.svg</file>
<file>folder-symbolic.svg</file>
<file>settings-symbolic.svg</file>
</gresource>
</gresources>

View File

@ -1,351 +0,0 @@
/*
* Fast but bad debayer method that scales and rotates by skipping source
* pixels and doesn't interpolate any values at all
*/
#include "quickpreview.h"
#include <assert.h>
#include <stdio.h>
/* Linear -> sRGB lookup table */
static const int srgb[] = {
0, 12, 21, 28, 33, 38, 42, 46, 49, 52, 55, 58, 61, 63, 66, 68, 70,
73, 75, 77, 79, 81, 82, 84, 86, 88, 89, 91, 93, 94, 96, 97, 99, 100,
102, 103, 104, 106, 107, 109, 110, 111, 112, 114, 115, 116, 117, 118,
120, 121, 122, 123, 124, 125, 126, 127, 129, 130, 131, 132, 133, 134,
135, 136, 137, 138, 139, 140, 141, 142, 142, 143, 144, 145, 146, 147,
148, 149, 150, 151, 151, 152, 153, 154, 155, 156, 157, 157, 158, 159,
160, 161, 161, 162, 163, 164, 165, 165, 166, 167, 168, 168, 169, 170,
171, 171, 172, 173, 174, 174, 175, 176, 176, 177, 178, 179, 179, 180,
181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189,
190, 191, 191, 192, 193, 193, 194, 194, 195, 196, 196, 197, 197, 198,
199, 199, 200, 201, 201, 202, 202, 203, 204, 204, 205, 205, 206, 206,
207, 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214,
215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222,
222, 223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229,
230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236,
237, 237, 237, 238, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243,
243, 244, 244, 245, 245, 245, 246, 246, 247, 247, 248, 248, 249, 249,
250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255
};
static inline uint32_t
pack_rgb(uint8_t r, uint8_t g, uint8_t b)
{
return (r << 16) | (g << 8) | b;
}
static inline uint32_t
convert_yuv_to_srgb(uint8_t y, uint8_t u, uint8_t v)
{
uint32_t r = 1.164f * y + 1.596f * (v - 128);
uint32_t g = 1.164f * y - 0.813f * (v - 128) - 0.391f * (u - 128);
uint32_t b = 1.164f * y + 2.018f * (u - 128);
return pack_rgb(r, g, b);
}
static inline uint32_t
apply_colormatrix(uint32_t color, const float *colormatrix)
{
if (!colormatrix) {
return color;
}
uint32_t r = (color >> 16) * colormatrix[0] +
((color >> 8) & 0xFF) * colormatrix[1] +
(color & 0xFF) * colormatrix[2];
uint32_t g = (color >> 16) * colormatrix[3] +
((color >> 8) & 0xFF) * colormatrix[4] +
(color & 0xFF) * colormatrix[5];
uint32_t b = (color >> 16) * colormatrix[6] +
((color >> 8) & 0xFF) * colormatrix[7] +
(color & 0xFF) * colormatrix[8];
// Clip colors
if (r > 0xFF)
r = 0xFF;
if (g > 0xFF)
g = 0xFF;
if (b > 0xFF)
b = 0xFF;
return pack_rgb(r, g, b);
}
static inline uint32_t
coord_map(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int rotation,
bool mirrored)
{
uint32_t x_r, y_r;
if (rotation == 0) {
x_r = x;
y_r = y;
} else if (rotation == 90) {
x_r = y;
y_r = height - x - 1;
} else if (rotation == 270) {
x_r = width - y - 1;
y_r = x;
} else {
x_r = width - x - 1;
y_r = height - y - 1;
}
if (mirrored) {
x_r = width - x_r - 1;
}
uint32_t index = y_r * width + x_r;
#ifdef DEBUG
assert(index < width * height);
#endif
return index;
}
static void
quick_preview_rggb8(uint32_t *dst, const uint32_t dst_width,
const uint32_t dst_height, const uint8_t *src,
const uint32_t src_width, const uint32_t src_height,
const MPPixelFormat format, const uint32_t rotation,
const bool mirrored, const float *colormatrix,
const uint8_t blacklevel, const uint32_t skip)
{
uint32_t src_y = 0, dst_y = 0;
while (src_y < src_height) {
uint32_t src_x = 0, dst_x = 0;
while (src_x < src_width) {
uint32_t src_i = src_y * src_width + src_x;
uint8_t b0 = srgb[src[src_i] - blacklevel];
uint8_t b1 = srgb[src[src_i + 1] - blacklevel];
uint8_t b2 = srgb[src[src_i + src_width + 1] - blacklevel];
uint32_t color;
switch (format) {
case MP_PIXEL_FMT_BGGR8:
color = pack_rgb(b2, b1, b0);
break;
case MP_PIXEL_FMT_GBRG8:
color = pack_rgb(b2, b0, b1);
break;
case MP_PIXEL_FMT_GRBG8:
color = pack_rgb(b1, b0, b2);
break;
case MP_PIXEL_FMT_RGGB8:
color = pack_rgb(b0, b1, b2);
break;
default:
assert(false);
}
color = apply_colormatrix(color, colormatrix);
dst[coord_map(dst_x, dst_y, dst_width, dst_height, rotation,
mirrored)] = color;
src_x += 2 + 2 * skip;
++dst_x;
}
src_y += 2 + 2 * skip;
++dst_y;
}
}
static void
quick_preview_rggb10(uint32_t *dst, const uint32_t dst_width,
const uint32_t dst_height, const uint8_t *src,
const uint32_t src_width, const uint32_t src_height,
const MPPixelFormat format, const uint32_t rotation,
const bool mirrored, const float *colormatrix,
const uint8_t blacklevel, const uint32_t skip)
{
assert(src_width % 2 == 0);
uint32_t width_bytes = mp_pixel_format_width_to_bytes(format, src_width);
uint32_t src_y = 0, dst_y = 0;
while (src_y < src_height) {
uint32_t src_x = 0, dst_x = 0;
while (src_x < width_bytes) {
uint32_t src_i = src_y * width_bytes + src_x;
uint8_t b0 = srgb[src[src_i] - blacklevel];
uint8_t b1 = srgb[src[src_i + 1] - blacklevel];
uint8_t b2 = srgb[src[src_i + width_bytes + 1] - blacklevel];
uint32_t color;
switch (format) {
case MP_PIXEL_FMT_BGGR10P:
color = pack_rgb(b2, b1, b0);
break;
case MP_PIXEL_FMT_GBRG10P:
color = pack_rgb(b2, b0, b1);
break;
case MP_PIXEL_FMT_GRBG10P:
color = pack_rgb(b1, b0, b2);
break;
case MP_PIXEL_FMT_RGGB10P:
color = pack_rgb(b0, b1, b2);
break;
default:
assert(false);
}
color = apply_colormatrix(color, colormatrix);
dst[coord_map(dst_x, dst_y, dst_width, dst_height, rotation,
mirrored)] = color;
uint32_t advance = 1 + skip;
if (src_x % 5 == 0) {
src_x += 2 * (advance % 2) + 5 * (advance / 2);
} else {
src_x += 3 * (advance % 2) + 5 * (advance / 2);
}
++dst_x;
}
src_y += 2 + 2 * skip;
++dst_y;
}
}
static void
quick_preview_yuv(uint32_t *dst, const uint32_t dst_width, const uint32_t dst_height,
const uint8_t *src, const uint32_t src_width,
const uint32_t src_height, const MPPixelFormat format,
const uint32_t rotation, const bool mirrored,
const float *colormatrix, const uint32_t skip)
{
assert(src_width % 2 == 0);
uint32_t width_bytes = src_width * 2;
uint32_t unrot_dst_width = dst_width;
if (rotation != 0 && rotation != 180) {
unrot_dst_width = dst_height;
}
uint32_t src_y = 0, dst_y = 0;
while (src_y < src_height) {
uint32_t src_x = 0, dst_x = 0;
while (src_x < width_bytes) {
uint32_t src_i = src_y * width_bytes + src_x;
uint8_t b0 = src[src_i];
uint8_t b1 = src[src_i + 1];
uint8_t b2 = src[src_i + 2];
uint8_t b3 = src[src_i + 3];
uint32_t color1, color2;
switch (format) {
case MP_PIXEL_FMT_UYVY:
color1 = convert_yuv_to_srgb(b1, b0, b2);
color2 = convert_yuv_to_srgb(b3, b0, b2);
break;
case MP_PIXEL_FMT_YUYV:
color1 = convert_yuv_to_srgb(b0, b1, b3);
color2 = convert_yuv_to_srgb(b2, b1, b3);
break;
default:
assert(false);
}
color1 = apply_colormatrix(color1, colormatrix);
color2 = apply_colormatrix(color2, colormatrix);
uint32_t dst_i1 = coord_map(dst_x, dst_y, dst_width,
dst_height, rotation, mirrored);
dst[dst_i1] = color1;
++dst_x;
// The last pixel needs to be skipped if we have an odd un-rotated width
if (dst_x < unrot_dst_width) {
uint32_t dst_i2 =
coord_map(dst_x, dst_y, dst_width,
dst_height, rotation, mirrored);
dst[dst_i2] = color2;
++dst_x;
}
src_x += 4 + 4 * skip;
}
src_y += 1 + skip;
++dst_y;
}
}
void
quick_preview(uint32_t *dst, const uint32_t dst_width, const uint32_t dst_height,
const uint8_t *src, const uint32_t src_width,
const uint32_t src_height, const MPPixelFormat format,
const uint32_t rotation, const bool mirrored, const float *colormatrix,
const uint8_t blacklevel, const uint32_t skip)
{
switch (format) {
case MP_PIXEL_FMT_BGGR8:
case MP_PIXEL_FMT_GBRG8:
case MP_PIXEL_FMT_GRBG8:
case MP_PIXEL_FMT_RGGB8:
quick_preview_rggb8(dst, dst_width, dst_height, src, src_width,
src_height, format, rotation, mirrored,
colormatrix, blacklevel, skip);
break;
case MP_PIXEL_FMT_BGGR10P:
case MP_PIXEL_FMT_GBRG10P:
case MP_PIXEL_FMT_GRBG10P:
case MP_PIXEL_FMT_RGGB10P:
quick_preview_rggb10(dst, dst_width, dst_height, src, src_width,
src_height, format, rotation, mirrored,
colormatrix, blacklevel, skip);
break;
case MP_PIXEL_FMT_UYVY:
case MP_PIXEL_FMT_YUYV:
quick_preview_yuv(dst, dst_width, dst_height, src, src_width,
src_height, format, rotation, mirrored,
colormatrix, skip);
break;
default:
assert(false);
}
}
static uint32_t
div_ceil(uint32_t x, uint32_t y)
{
return x / y + !!(x % y);
}
void
quick_preview_size(uint32_t *dst_width, uint32_t *dst_height, uint32_t *skip,
const uint32_t preview_width, const uint32_t preview_height,
const uint32_t src_width, const uint32_t src_height,
const MPPixelFormat format, const int rotation)
{
uint32_t colors_x = mp_pixel_format_width_to_colors(format, src_width);
uint32_t colors_y = mp_pixel_format_height_to_colors(format, src_height);
if (rotation != 0 && rotation != 180) {
uint32_t tmp = colors_x;
colors_x = colors_y;
colors_y = tmp;
}
uint32_t scale_x = colors_x / preview_width;
uint32_t scale_y = colors_y / preview_height;
if (scale_x > 0)
--scale_x;
if (scale_y > 0)
--scale_y;
*skip = scale_x > scale_y ? scale_x : scale_y;
*dst_width = div_ceil(colors_x, (1 + *skip));
if (*dst_width <= 0)
*dst_width = 1;
*dst_height = div_ceil(colors_y, (1 + *skip));
if (*dst_height <= 0)
*dst_height = 1;
}

View File

@ -1,14 +0,0 @@
#include "camera.h"
#include <stdint.h>
void quick_preview(uint32_t *dst, const uint32_t dst_width,
const uint32_t dst_height, const uint8_t *src,
const uint32_t src_width, const uint32_t src_height,
const MPPixelFormat format, const uint32_t rotation,
const bool mirrored, const float *colormatrix,
const uint8_t blacklevel, const uint32_t skip);
void quick_preview_size(uint32_t *dst_width, uint32_t *dst_height, uint32_t *skip,
const uint32_t preview_width, const uint32_t preview_height,
const uint32_t src_width, const uint32_t src_height,
const MPPixelFormat format, const int rotation);

View File

@ -6,6 +6,7 @@
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#define MAX_VIDEO_BUFFERS 20
@ -205,6 +206,7 @@ xioctl(int fd, int request, void *arg)
struct video_buffer {
uint32_t length;
uint8_t *data;
int fd;
};
struct _MPCamera {
@ -465,6 +467,17 @@ mp_camera_start_capture(MPCamera *camera)
break;
}
struct v4l2_exportbuffer expbuf = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.index = i,
};
if (xioctl(camera->video_fd, VIDIOC_EXPBUF, &expbuf) == -1) {
errno_printerr("VIDIOC_EXPBUF");
break;
}
camera->buffers[i].fd = expbuf.fd;
++camera->num_buffers;
}
@ -510,6 +523,10 @@ error:
-1) {
errno_printerr("munmap");
}
if (close(camera->buffers[i].fd) == -1) {
errno_printerr("close");
}
}
// Reset allocated buffers
@ -543,6 +560,10 @@ mp_camera_stop_capture(MPCamera *camera)
-1) {
errno_printerr("munmap");
}
if (close(camera->buffers[i].fd) == -1) {
errno_printerr("close");
}
}
camera->num_buffers = 0;
@ -565,8 +586,7 @@ mp_camera_is_capturing(MPCamera *camera)
}
bool
mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *),
void *user_data)
mp_camera_capture_buffer(MPCamera *camera, MPBuffer *buffer)
{
struct v4l2_buffer buf = {};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
@ -587,6 +607,7 @@ mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *),
/* fallthrough */
default:
errno_printerr("VIDIOC_DQBUF");
exit(1);
return false;
}
}
@ -606,24 +627,23 @@ mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *),
mp_pixel_format_width_to_bytes(pixel_format, width) * height);
assert(bytesused == camera->buffers[buf.index].length);
MPImage image = {
.pixel_format = pixel_format,
.width = width,
.height = height,
.data = camera->buffers[buf.index].data,
};
buffer->index = buf.index;
buffer->data = camera->buffers[buf.index].data;
buffer->fd = camera->buffers[buf.index].fd;
callback(image, user_data);
return true;
}
// The callback may have stopped the capture, only queue the buffer if we're
// still capturing.
if (mp_camera_is_capturing(camera)) {
if (xioctl(camera->video_fd, VIDIOC_QBUF, &buf) == -1) {
errno_printerr("VIDIOC_QBUF");
return false;
}
bool mp_camera_release_buffer(MPCamera *camera, uint32_t buffer_index)
{
struct v4l2_buffer buf = {};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = buffer_index;
if (xioctl(camera->video_fd, VIDIOC_QBUF, &buf) == -1) {
errno_printerr("VIDIOC_QBUF");
return false;
}
return true;
}

View File

@ -45,11 +45,11 @@ typedef struct {
bool mp_camera_mode_is_equivalent(const MPCameraMode *m1, const MPCameraMode *m2);
typedef struct {
uint32_t pixel_format;
uint32_t width;
uint32_t height;
uint32_t index;
uint8_t *data;
} MPImage;
int fd;
} MPBuffer;
typedef struct _MPCamera MPCamera;
@ -67,8 +67,8 @@ bool mp_camera_set_mode(MPCamera *camera, MPCameraMode *mode);
bool mp_camera_start_capture(MPCamera *camera);
bool mp_camera_stop_capture(MPCamera *camera);
bool mp_camera_is_capturing(MPCamera *camera);
bool mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *),
void *user_data);
bool mp_camera_capture_buffer(MPCamera *camera, MPBuffer *buffer);
bool mp_camera_release_buffer(MPCamera *camera, uint32_t buffer_index);
typedef struct _MPCameraModeList MPCameraModeList;

View File

@ -3,7 +3,6 @@
#include "ini.h"
#include "config.h"
#include "matrix.h"
#include <wordexp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -21,18 +20,8 @@ static bool
find_config(char *conffile)
{
char buf[512];
char *xdg_config_home;
wordexp_t exp_result;
FILE *fp;
// Resolve XDG stuff
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) {
xdg_config_home = "~/.config";
}
wordexp(xdg_config_home, &exp_result, 0);
xdg_config_home = strdup(exp_result.we_wordv[0]);
wordfree(&exp_result);
if (access("/proc/device-tree/compatible", F_OK) != -1) {
// Reads to compatible string of the current device tree, looks like:
// pine64,pinephone-1.2\0allwinner,sun50i-a64\0
@ -48,7 +37,7 @@ find_config(char *conffile)
}
// Check for a config file in XDG_CONFIG_HOME
sprintf(conffile, "%s/megapixels/config/%s.ini", xdg_config_home,
sprintf(conffile, "%s/megapixels/config/%s.ini", g_get_user_config_dir (),
buf);
if (access(conffile, F_OK) != -1) {
printf("Found config file at %s\n", conffile);

220
src/gl_util.c Normal file
View File

@ -0,0 +1,220 @@
#include "gl_util.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <gio/gio.h>
#include <gmodule.h>
#include <gdk/gdk.h>
void gl_util_check_error(const char *file, int line)
{
GLenum error = glGetError();
const char *name;
switch (error) {
case GL_NO_ERROR:
return; // no error
case GL_INVALID_ENUM:
name = "GL_INVALID_ENUM";
break;
case GL_INVALID_VALUE:
name = "GL_INVALID_VALUE";
break;
case GL_INVALID_OPERATION:
name = "GL_INVALID_OPERATION";
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
name = "GL_INVALID_FRAMEBUFFER_OPERATION";
break;
case GL_OUT_OF_MEMORY:
name = "GL_OUT_OF_MEMORY";
break;
default:
name = "UNKNOWN ERROR!";
break;
}
printf("GL error at %s:%d - %s\n", file, line, name);
// raise(SIGTRAP);
}
GLuint
gl_util_load_shader(const char *resource, GLenum type, const char **extra_sources, size_t num_extra)
{
GdkGLContext *context = gdk_gl_context_get_current();
assert(context);
GLuint shader = glCreateShader(type);
if (shader == 0) {
return 0;
}
GBytes *bytes = g_resources_lookup_data(resource, 0, NULL);
if (!bytes) {
printf("Failed to load shader resource %s\n", resource);
return 0;
}
// Build #define for OpenGL context information
gboolean is_es = gdk_gl_context_get_use_es(context);
int major, minor;
gdk_gl_context_get_version(context, &major, &minor);
char context_info_buf[128];
snprintf(context_info_buf, 128, "#define %s\n#define GL_%d\n#define GL_%d_%d\n", is_es ? "GL_ES" : "GL_NO_ES", major, major, minor);
gsize glib_size = 0;
const GLchar *source = g_bytes_get_data(bytes, &glib_size);
if (glib_size == 0 || glib_size > INT_MAX) {
printf("Invalid size for resource\n");
return 0;
}
const GLchar **sources = malloc((num_extra + 1) * sizeof(GLchar *));
GLint *sizes = malloc((num_extra + 1) * sizeof(GLint));
for (size_t i = 0; i < num_extra; ++i) {
sources[i] = extra_sources[i];
sizes[i] = -1;
}
sources[num_extra] = source;
sizes[num_extra] = glib_size;
glShaderSource(shader, num_extra + 1, sources, sizes);
glCompileShader(shader);
check_gl();
free(sources);
free(sizes);
g_bytes_unref(bytes);
// Check compile status
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (success == GL_FALSE) {
printf("Shader compilation failed for %s\n", resource);
glDeleteShader(shader);
return 0;
}
GLint log_length;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
if (log_length > 0) {
char *log = malloc(sizeof(char) * log_length);
glGetShaderInfoLog(shader, log_length - 1, &log_length, log);
printf("Shader %s log: %s\n", resource, log);
free(log);
glDeleteShader(shader);
return 0;
}
return shader;
}
GLuint
gl_util_link_program(GLuint *shaders, size_t num_shaders)
{
GLuint program = glCreateProgram();
for (size_t i = 0; i < num_shaders; ++i) {
glAttachShader(program, shaders[i]);
}
glLinkProgram(program);
check_gl();
GLint success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (success == GL_FALSE) {
printf("Program linking failed\n");
}
GLint log_length;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
if (log_length > 0) {
char *log = malloc(sizeof(char) * log_length);
glGetProgramInfoLog(program, log_length - 1, &log_length, log);
printf("Program log: %s\n", log);
free(log);
}
check_gl();
return program;
}
static const GLfloat quad_data[] = {
// Vertices
-1, -1,
1, -1,
-1, 1,
1, 1,
// Texcoords
0, 0,
1, 0,
0, 1,
1, 1,
};
GLuint gl_util_new_quad()
{
GdkGLContext *context = gdk_gl_context_get_current();
assert(context);
if (gdk_gl_context_get_use_es(context)) {
return 0;
} else {
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad_data), quad_data, GL_STATIC_DRAW);
check_gl();
glBindBuffer(GL_ARRAY_BUFFER, 0);
check_gl();
return buffer;
}
}
void gl_util_bind_quad(GLuint buffer)
{
GdkGLContext *context = gdk_gl_context_get_current();
assert(context);
if (gdk_gl_context_get_use_es(context)) {
glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, quad_data);
check_gl();
glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
check_gl();
glVertexAttribPointer(GL_UTIL_TEX_COORD_ATTRIBUTE, 2, GL_FLOAT, 0, 0, quad_data + 8);
check_gl();
glEnableVertexAttribArray(GL_UTIL_TEX_COORD_ATTRIBUTE);
check_gl();
} else {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
check_gl();
glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
check_gl();
glVertexAttribPointer(GL_UTIL_TEX_COORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, (void*) (8 * sizeof(float)));
glEnableVertexAttribArray(GL_UTIL_TEX_COORD_ATTRIBUTE);
check_gl();
}
}
void gl_util_draw_quad(GLuint buffer)
{
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
check_gl();
}

17
src/gl_util.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <epoxy/gl.h>
#include <stddef.h>
#define GL_UTIL_VERTEX_ATTRIBUTE 0
#define GL_UTIL_TEX_COORD_ATTRIBUTE 1
#define check_gl() gl_util_check_error(__FILE__, __LINE__)
void gl_util_check_error(const char *file, int line);
GLuint gl_util_load_shader(const char *resource, GLenum type, const char **extra_sources, size_t num_extra);
GLuint gl_util_link_program(GLuint *shaders, size_t num_shaders);
GLuint gl_util_new_quad();
void gl_util_bind_quad(GLuint buffer);
void gl_util_draw_quad(GLuint buffer);

141
src/gles2_debayer.c Normal file
View File

@ -0,0 +1,141 @@
#include "gles2_debayer.h"
#include "camera.h"
#include "gl_util.h"
#include <stdlib.h>
#define VERTEX_ATTRIBUTE 0
#define TEX_COORD_ATTRIBUTE 1
struct _GLES2Debayer {
GLuint frame_buffer;
GLuint program;
GLuint uniform_transform;
GLuint uniform_pixel_size;
GLuint uniform_texture;
GLuint uniform_color_matrix;
GLuint quad;
};
GLES2Debayer *
gles2_debayer_new(MPPixelFormat format)
{
if (format != MP_PIXEL_FMT_BGGR8) {
return NULL;
}
GLuint frame_buffer;
glGenFramebuffers(1, &frame_buffer);
check_gl();
GLuint shaders[] = {
gl_util_load_shader("/org/postmarketos/Megapixels/debayer.vert", GL_VERTEX_SHADER, NULL, 0),
gl_util_load_shader("/org/postmarketos/Megapixels/debayer.frag", GL_FRAGMENT_SHADER, NULL, 0),
};
GLuint program = gl_util_link_program(shaders, 2);
glBindAttribLocation(program, VERTEX_ATTRIBUTE, "vert");
glBindAttribLocation(program, TEX_COORD_ATTRIBUTE, "tex_coord");
check_gl();
GLES2Debayer *self = malloc(sizeof(GLES2Debayer));
self->frame_buffer = frame_buffer;
self->program = program;
self->uniform_transform = glGetUniformLocation(self->program, "transform");
self->uniform_pixel_size = glGetUniformLocation(self->program, "pixel_size");
self->uniform_texture = glGetUniformLocation(self->program, "texture");
self->uniform_color_matrix = glGetUniformLocation(self->program, "color_matrix");
check_gl();
self->quad = gl_util_new_quad();
return self;
}
void
gles2_debayer_free(GLES2Debayer *self)
{
glDeleteFramebuffers(1, &self->frame_buffer);
glDeleteProgram(self->program);
free(self);
}
void
gles2_debayer_use(GLES2Debayer *self)
{
glUseProgram(self->program);
check_gl();
gl_util_bind_quad(self->quad);
}
void
gles2_debayer_configure(GLES2Debayer *self,
const uint32_t dst_width, const uint32_t dst_height,
const uint32_t src_width, const uint32_t src_height,
const uint32_t rotation,
const bool mirrored,
const float *colormatrix,
const uint8_t blacklevel)
{
glViewport(0, 0, dst_width, dst_height);
check_gl();
GLfloat rotation_list[4] = { 0, -1, 0, 1 };
int rotation_index = 4 - rotation / 90;
GLfloat sin_rot = rotation_list[rotation_index];
GLfloat cos_rot = rotation_list[(rotation_index + 1) % 4];
GLfloat scale_x = mirrored ? 1 : -1;
GLfloat matrix[9] = {
cos_rot * scale_x, sin_rot, 0,
-sin_rot * scale_x, cos_rot, 0,
0, 0, 1,
};
glUniformMatrix3fv(self->uniform_transform, 1, GL_FALSE, matrix);
check_gl();
GLfloat pixel_size_x = 1.0f / src_width;
GLfloat pixel_size_y = 1.0f / src_height;
glUniform2f(self->uniform_pixel_size, pixel_size_x, pixel_size_y);
check_gl();
if (colormatrix) {
GLfloat transposed[9];
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 3; ++j)
transposed[i + j * 3] = colormatrix[j + i * 3];
glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, transposed);
} else {
static const GLfloat identity[9] = {
1, 0, 0,
0, 1, 0,
0, 0, 1,
};
glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, identity);
}
check_gl();
}
void
gles2_debayer_process(GLES2Debayer *self, GLuint dst_id, GLuint source_id)
{
glBindFramebuffer(GL_FRAMEBUFFER, self->frame_buffer);
glBindTexture(GL_TEXTURE_2D, dst_id);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_id, 0);
check_gl();
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, source_id);
glUniform1i(self->uniform_texture, 0);
check_gl();
gl_util_draw_quad(self->quad);
}

21
src/gles2_debayer.h Normal file
View File

@ -0,0 +1,21 @@
#include "camera.h"
#include "gl_util.h"
#include <assert.h>
#include <stdio.h>
typedef struct _GLES2Debayer GLES2Debayer;
GLES2Debayer* gles2_debayer_new(MPPixelFormat format);
void gles2_debayer_free(GLES2Debayer *self);
void gles2_debayer_use(GLES2Debayer *self);
void gles2_debayer_configure(GLES2Debayer *self,
const uint32_t dst_width, const uint32_t dst_height,
const uint32_t src_width, const uint32_t src_height,
const uint32_t rotation,
const bool mirrored,
const float *colormatrix,
const uint8_t blacklevel);
void gles2_debayer_process(GLES2Debayer *self, GLuint dst_id, GLuint source_id);

View File

View File

View File

@ -75,6 +75,8 @@ static int captures_remaining = 0;
static int preview_width;
static int preview_height;
static int device_rotation;
struct control_state {
bool gain_is_manual;
int gain;
@ -185,6 +187,12 @@ setup_camera(MPDeviceList **device_list, const struct mp_camera_config *config)
info->camera = mp_camera_new(dev_info->video_fd, info->fd);
// Start with the capture format, this works around a bug with
// the ov5640 driver where it won't allow setting the preview
// format initially.
MPCameraMode mode = config->capture_mode;
mp_camera_set_mode(info->camera, &mode);
// Trigger continuous auto focus if the sensor supports it
if (mp_camera_query_control(info->camera, V4L2_CID_FOCUS_AUTO,
NULL)) {
@ -269,6 +277,7 @@ update_process_pipeline()
.burst_length = burst_length,
.preview_width = preview_width,
.preview_height = preview_height,
.device_rotation = device_rotation,
.gain_is_manual = current_controls.gain_is_manual,
.gain = current_controls.gain,
.gain_max = info->gain_max,
@ -305,6 +314,7 @@ capture(MPPipeline *pipeline, const void *data)
V4L2_EXPOSURE_MANUAL);
// Change camera mode for capturing
mp_process_pipeline_sync();
mp_camera_stop_capture(info->camera);
mode = camera->capture_mode;
@ -324,6 +334,19 @@ mp_io_pipeline_capture()
mp_pipeline_invoke(pipeline, capture, NULL, 0);
}
static void
release_buffer(MPPipeline *pipeline, const uint32_t *buffer_index)
{
struct camera_info *info = &cameras[camera->index];
mp_camera_release_buffer(info->camera, *buffer_index);
}
void mp_io_pipeline_release_buffer(uint32_t buffer_index)
{
mp_pipeline_invoke(pipeline, (MPPipelineCallback) release_buffer, &buffer_index, sizeof(uint32_t));
}
static void
update_controls()
{
@ -375,7 +398,7 @@ update_controls()
}
static void
on_frame(MPImage image, void *data)
on_frame(MPBuffer buffer, void * _data)
{
// Only update controls right after a frame was captured
update_controls();
@ -384,13 +407,13 @@ on_frame(MPImage image, void *data)
// presumably from buffers made ready during the switch. Ignore these.
if (just_switched_mode) {
if (blank_frame_count < 20) {
// Only check a 50x50 area
// Only check a 10x10 area
size_t test_size =
MIN(50, image.width) * MIN(50, image.height);
MIN(10, mode.width) * MIN(10, mode.height);
bool image_is_blank = true;
for (size_t i = 0; i < test_size; ++i) {
if (image.data[i] != 0) {
if (buffer.data[i] != 0) {
image_is_blank = false;
}
}
@ -407,17 +430,8 @@ on_frame(MPImage image, void *data)
blank_frame_count = 0;
}
// Copy from the camera buffer
size_t size =
mp_pixel_format_width_to_bytes(image.pixel_format, image.width) *
image.height;
uint8_t *buffer = malloc(size);
memcpy(buffer, image.data, size);
image.data = buffer;
// Send the image off for processing
mp_process_pipeline_process_image(image);
mp_process_pipeline_process_image(buffer);
if (captures_remaining > 0) {
--captures_remaining;
@ -438,6 +452,7 @@ on_frame(MPImage image, void *data)
}
// Go back to preview mode
mp_process_pipeline_sync();
mp_camera_stop_capture(info->camera);
mode = camera->preview_mode;
@ -465,6 +480,7 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state)
struct camera_info *info = &cameras[camera->index];
struct device_info *dev_info = &devices[info->device_index];
mp_process_pipeline_sync();
mp_camera_stop_capture(info->camera);
mp_device_setup_link(dev_info->device, info->pad_id,
dev_info->interface_pad_id, false);
@ -492,15 +508,15 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state)
pipeline, info->camera, on_frame, NULL);
current_controls.gain_is_manual =
mp_camera_control_get_int32(
info->camera, V4L2_CID_EXPOSURE_AUTO) ==
V4L2_EXPOSURE_MANUAL;
mp_camera_control_get_bool(info->camera,
V4L2_CID_AUTOGAIN) == 0;
current_controls.gain = mp_camera_control_get_int32(
info->camera, info->gain_ctrl);
current_controls.exposure_is_manual =
mp_camera_control_get_bool(info->camera,
V4L2_CID_AUTOGAIN) == 0;
mp_camera_control_get_int32(
info->camera, V4L2_CID_EXPOSURE_AUTO) ==
V4L2_EXPOSURE_MANUAL;
current_controls.exposure = mp_camera_control_get_int32(
info->camera, V4L2_CID_EXPOSURE);
}
@ -508,11 +524,13 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state)
has_changed = has_changed || burst_length != state->burst_length ||
preview_width != state->preview_width ||
preview_height != state->preview_height;
preview_height != state->preview_height ||
device_rotation != state->device_rotation;
burst_length = state->burst_length;
preview_width = state->preview_width;
preview_height = state->preview_height;
device_rotation = state->device_rotation;
if (camera) {
struct control_state previous_desired = desired_controls;

View File

@ -10,6 +10,8 @@ struct mp_io_pipeline_state {
int preview_width;
int preview_height;
int device_rotation;
bool gain_is_manual;
int gain;
@ -23,4 +25,6 @@ void mp_io_pipeline_stop();
void mp_io_pipeline_focus();
void mp_io_pipeline_capture();
void mp_io_pipeline_release_buffer(uint32_t buffer_index);
void mp_io_pipeline_update_state(const struct mp_io_pipeline_state *state);

963
src/main.c Normal file
View File

@ -0,0 +1,963 @@
#include "main.h"
#include <errno.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <linux/media.h>
#include <linux/v4l2-subdev.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#include <assert.h>
#include <limits.h>
#include <linux/kdev_t.h>
#include <sys/sysmacros.h>
#include <asm/errno.h>
#include <wordexp.h>
#include <gtk/gtk.h>
#include <locale.h>
#include <zbar.h>
#include "gl_util.h"
#include "camera_config.h"
#include "io_pipeline.h"
#include "process_pipeline.h"
#define RENDERDOC
#ifdef RENDERDOC
#include <dlfcn.h>
#include <renderdoc/app.h>
RENDERDOC_API_1_1_2 *rdoc_api = NULL;
#endif
enum user_control { USER_CONTROL_ISO, USER_CONTROL_SHUTTER };
static bool camera_is_initialized = false;
static const struct mp_camera_config *camera = NULL;
static MPCameraMode mode;
static int preview_width = -1;
static int preview_height = -1;
static int device_rotation = 0;
static bool gain_is_manual = false;
static int gain;
static int gain_max;
static bool exposure_is_manual = false;
static int exposure;
static bool has_auto_focus_continuous;
static bool has_auto_focus_start;
static MPProcessPipelineBuffer *current_preview_buffer = NULL;
static int preview_buffer_width = -1;
static int preview_buffer_height = -1;
static char last_path[260] = "";
static MPZBarScanResult *zbar_result = NULL;
static int burst_length = 3;
// Widgets
GtkWidget *preview;
GtkWidget *main_stack;
GtkWidget *open_last_stack;
GtkWidget *thumb_last;
GtkWidget *process_spinner;
GtkWidget *scanned_codes;
GtkWidget *preview_top_box;
GtkWidget *preview_bottom_box;
int
remap(int value, int input_min, int input_max, int output_min, int output_max)
{
const long long factor = 1000000000;
long long output_spread = output_max - output_min;
long long input_spread = input_max - input_min;
long long zero_value = value - input_min;
zero_value *= factor;
long long percentage = zero_value / input_spread;
long long zero_output = percentage * output_spread / factor;
long long result = output_min + zero_output;
return (int)result;
}
static void
update_io_pipeline()
{
struct mp_io_pipeline_state io_state = {
.camera = camera,
.burst_length = burst_length,
.preview_width = preview_width,
.preview_height = preview_height,
.device_rotation = device_rotation,
.gain_is_manual = gain_is_manual,
.gain = gain,
.exposure_is_manual = exposure_is_manual,
.exposure = exposure,
};
mp_io_pipeline_update_state(&io_state);
}
static bool
update_state(const struct mp_main_state *state)
{
if (!camera_is_initialized) {
camera_is_initialized = true;
}
if (camera == state->camera) {
mode = state->mode;
if (!gain_is_manual) {
gain = state->gain;
}
gain_max = state->gain_max;
if (!exposure_is_manual) {
exposure = state->exposure;
}
has_auto_focus_continuous = state->has_auto_focus_continuous;
has_auto_focus_start = state->has_auto_focus_start;
}
preview_buffer_width = state->image_width;
preview_buffer_height = state->image_height;
return false;
}
void
mp_main_update_state(const struct mp_main_state *state)
{
struct mp_main_state *state_copy = malloc(sizeof(struct mp_main_state));
*state_copy = *state;
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
(GSourceFunc)update_state, state_copy, free);
}
static bool set_zbar_result(MPZBarScanResult *result)
{
if (zbar_result) {
for (uint8_t i = 0; i < zbar_result->size; ++i) {
free(zbar_result->codes[i].data);
}
free(zbar_result);
}
zbar_result = result;
gtk_widget_queue_draw(preview);
return false;
}
void mp_main_set_zbar_result(MPZBarScanResult *result)
{
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
(GSourceFunc)set_zbar_result, result, NULL);
}
static bool
set_preview(MPProcessPipelineBuffer *buffer)
{
if (current_preview_buffer) {
mp_process_pipeline_buffer_unref(current_preview_buffer);
}
current_preview_buffer = buffer;
gtk_widget_queue_draw(preview);
return false;
}
void
mp_main_set_preview(MPProcessPipelineBuffer *buffer)
{
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
(GSourceFunc)set_preview, buffer, NULL);
}
struct capture_completed_args {
GdkTexture *thumb;
char *fname;
};
static bool
capture_completed(struct capture_completed_args *args)
{
strncpy(last_path, args->fname, 259);
gtk_image_set_from_paintable(GTK_IMAGE(thumb_last),
GDK_PAINTABLE(args->thumb));
gtk_spinner_stop(GTK_SPINNER(process_spinner));
gtk_stack_set_visible_child(GTK_STACK(open_last_stack), thumb_last);
g_object_unref(args->thumb);
g_free(args->fname);
return false;
}
void
mp_main_capture_completed(GdkTexture *thumb, const char *fname)
{
struct capture_completed_args *args = malloc(sizeof(struct capture_completed_args));
args->thumb = thumb;
args->fname = g_strdup(fname);
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
(GSourceFunc)capture_completed, args, free);
}
static GLuint blit_program;
static GLuint blit_uniform_transform;
static GLuint blit_uniform_texture;
static GLuint solid_program;
static GLuint solid_uniform_color;
static GLuint quad;
static void
preview_realize(GtkGLArea *area)
{
gtk_gl_area_make_current(area);
if (gtk_gl_area_get_error(area) != NULL) {
return;
}
// Make a VAO for OpenGL
if (!gtk_gl_area_get_use_es(area)) {
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
check_gl();
}
GLuint blit_shaders[] = {
gl_util_load_shader("/org/postmarketos/Megapixels/blit.vert", GL_VERTEX_SHADER, NULL, 0),
gl_util_load_shader("/org/postmarketos/Megapixels/blit.frag", GL_FRAGMENT_SHADER, NULL, 0),
};
blit_program = gl_util_link_program(blit_shaders, 2);
glBindAttribLocation(blit_program, GL_UTIL_VERTEX_ATTRIBUTE, "vert");
glBindAttribLocation(blit_program, GL_UTIL_TEX_COORD_ATTRIBUTE, "tex_coord");
check_gl();
blit_uniform_transform = glGetUniformLocation(blit_program, "transform");
blit_uniform_texture = glGetUniformLocation(blit_program, "texture");
GLuint solid_shaders[] = {
gl_util_load_shader("/org/postmarketos/Megapixels/solid.vert", GL_VERTEX_SHADER, NULL, 0),
gl_util_load_shader("/org/postmarketos/Megapixels/solid.frag", GL_FRAGMENT_SHADER, NULL, 0),
};
solid_program = gl_util_link_program(solid_shaders, 2);
glBindAttribLocation(solid_program, GL_UTIL_VERTEX_ATTRIBUTE, "vert");
check_gl();
solid_uniform_color = glGetUniformLocation(solid_program, "color");
quad = gl_util_new_quad();
}
static void
position_preview(float *offset_x, float *offset_y, float *size_x, float *size_y)
{
int buffer_width, buffer_height;
if (device_rotation == 0 || device_rotation == 180) {
buffer_width = preview_buffer_width;
buffer_height = preview_buffer_height;
} else {
buffer_width = preview_buffer_height;
buffer_height = preview_buffer_width;
}
int scale_factor = gtk_widget_get_scale_factor(preview);
int top_height = gtk_widget_get_allocated_height(preview_top_box) * scale_factor;
int bottom_height = gtk_widget_get_allocated_height(preview_bottom_box) * scale_factor;
int inner_height = preview_height - top_height - bottom_height;
double scale = MIN(preview_width / (float) buffer_width, preview_height / (float) buffer_height);
*size_x = scale * buffer_width;
*size_y = scale * buffer_height;
*offset_x = (preview_width - *size_x) / 2.0;
if (*size_y > inner_height) {
*offset_y = (preview_height - *size_y) / 2.0;
} else {
*offset_y = top_height + (inner_height - *size_y) / 2.0;
}
}
static gboolean
preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data)
{
if (gtk_gl_area_get_error(area) != NULL) {
return FALSE;
}
if (!camera_is_initialized) {
return FALSE;
}
#ifdef RENDERDOC
if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL);
#endif
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
float offset_x, offset_y, size_x, size_y;
position_preview(&offset_x, &offset_y, &size_x, &size_y);
glViewport(offset_x,
preview_height - size_y - offset_y,
size_x,
size_y);
if (current_preview_buffer) {
glUseProgram(blit_program);
GLfloat rotation_list[4] = { 0, -1, 0, 1 };
int rotation_index = device_rotation / 90;
GLfloat sin_rot = rotation_list[rotation_index];
GLfloat cos_rot = rotation_list[(4 + rotation_index - 1) % 4];
GLfloat matrix[9] = {
cos_rot, sin_rot, 0,
-sin_rot, cos_rot, 0,
0, 0, 1,
};
glUniformMatrix3fv(blit_uniform_transform, 1, GL_FALSE, matrix);
check_gl();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mp_process_pipeline_buffer_get_texture_id(current_preview_buffer));
glUniform1i(blit_uniform_texture, 0);
check_gl();
gl_util_bind_quad(quad);
gl_util_draw_quad(quad);
}
if (zbar_result) {
GLuint buffer;
if (!gtk_gl_area_get_use_es(area)) {
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
check_gl();
}
glUseProgram(solid_program);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glUniform4f(solid_uniform_color, 1, 0, 0, 0.5);
for (uint8_t i = 0; i < zbar_result->size; ++i) {
MPZBarCode *code = &zbar_result->codes[i];
GLfloat vertices[] = {
code->bounds_x[0], code->bounds_y[0],
code->bounds_x[1], code->bounds_y[1],
code->bounds_x[3], code->bounds_y[3],
code->bounds_x[2], code->bounds_y[2],
};
for (int i = 0; i < 4; ++i) {
vertices[i * 2] = 2 * vertices[i * 2] / preview_buffer_width - 1.0;
vertices[i * 2 + 1] = 1.0 - 2 * vertices[i * 2 + 1] / preview_buffer_height;
}
if (gtk_gl_area_get_use_es(area)) {
glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, vertices);
check_gl();
glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
check_gl();
} else {
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW);
check_gl();
glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
check_gl();
}
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
check_gl();
}
glDisable(GL_BLEND);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
glFlush();
#ifdef RENDERDOC
if(rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL);
#endif
return FALSE;
}
static gboolean
preview_resize(GtkWidget *widget, int width, int height, gpointer data)
{
if (preview_width != width || preview_height != height) {
preview_width = width;
preview_height = height;
update_io_pipeline();
}
return TRUE;
}
void
run_open_last_action(GSimpleAction *action, GVariant *param, gpointer user_data)
{
char uri[275];
GError *error = NULL;
if (strlen(last_path) == 0) {
return;
}
sprintf(uri, "file://%s", last_path);
if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
g_printerr("Could not launch image viewer for '%s': %s\n", uri, error->message);
}
}
void
run_open_photos_action(GSimpleAction *action, GVariant *param, gpointer user_data)
{
char uri[270];
GError *error = NULL;
sprintf(uri, "file://%s", g_get_user_special_dir(G_USER_DIRECTORY_PICTURES));
if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
g_printerr("Could not launch image viewer: %s\n", error->message);
}
}
void
run_capture_action(GSimpleAction *action, GVariant *param, gpointer user_data)
{
gtk_spinner_start(GTK_SPINNER(process_spinner));
gtk_stack_set_visible_child(GTK_STACK(open_last_stack), process_spinner);
mp_io_pipeline_capture();
}
void
run_quit_action(GSimpleAction *action, GVariant *param, GApplication *app)
{
g_application_quit(app);
}
static bool
check_point_inside_bounds(int x, int y, int *bounds_x, int *bounds_y)
{
bool right = false, left = false, top = false, bottom = false;
for (int i = 0; i < 4; ++i) {
if (x <= bounds_x[i])
left = true;
if (x >= bounds_x[i])
right = true;
if (y <= bounds_y[i])
top = true;
if (y >= bounds_y[i])
bottom = true;
}
return right && left && top && bottom;
}
static void
on_zbar_dialog_response(GtkDialog *dialog, int response, char *data)
{
GError *error = NULL;
switch (response) {
case GTK_RESPONSE_YES:
if (!g_app_info_launch_default_for_uri(data,
NULL, &error)) {
g_printerr("Could not launch application: %s\n",
error->message);
}
case GTK_RESPONSE_ACCEPT:
{
GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(dialog));
gdk_clipboard_set_text(
gdk_display_get_clipboard(display),
data);
}
case GTK_RESPONSE_CANCEL:
break;
default:
g_printerr("Wrong dialog response: %d\n", response);
}
g_free(data);
gtk_window_destroy(GTK_WINDOW(dialog));
}
static void
on_zbar_code_tapped(GtkWidget *widget, const MPZBarCode *code)
{
GtkWidget *dialog;
GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
bool data_is_url = g_uri_is_valid(
code->data, G_URI_FLAGS_PARSE_RELAXED, NULL);
char* data = strdup(code->data);
if (data_is_url) {
dialog = gtk_message_dialog_new(
GTK_WINDOW(gtk_widget_get_root(widget)),
flags,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_NONE,
"Found a URL '%s' encoded in a %s code.",
code->data,
code->type);
gtk_dialog_add_buttons(
GTK_DIALOG(dialog),
"_Open URL",
GTK_RESPONSE_YES,
NULL);
} else {
dialog = gtk_message_dialog_new(
GTK_WINDOW(gtk_widget_get_root(widget)),
flags,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_NONE,
"Found '%s' encoded in a %s code.",
code->data,
code->type);
}
gtk_dialog_add_buttons(
GTK_DIALOG(dialog),
"_Copy",
GTK_RESPONSE_ACCEPT,
"_Cancel",
GTK_RESPONSE_CANCEL,
NULL);
g_signal_connect(dialog, "response", G_CALLBACK(on_zbar_dialog_response), data);
gtk_widget_show(GTK_WIDGET(dialog));
}
static void
preview_pressed(GtkGestureClick *gesture, int n_press, double x, double y)
{
GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
int scale_factor = gtk_widget_get_scale_factor(widget);
// Tapped zbar result
if (zbar_result) {
// Transform the event coordinates to the image
float offset_x, offset_y, size_x, size_y;
position_preview(&offset_x, &offset_y, &size_x, &size_y);
int zbar_x = (x - offset_x) * scale_factor / size_x * preview_buffer_width;
int zbar_y = (y - offset_y) * scale_factor / size_y * preview_buffer_height;
for (uint8_t i = 0; i < zbar_result->size; ++i) {
MPZBarCode *code = &zbar_result->codes[i];
if (check_point_inside_bounds(zbar_x, zbar_y, code->bounds_x, code->bounds_y)) {
on_zbar_code_tapped(widget, code);
return;
}
}
}
// Tapped preview image itself, try focussing
if (has_auto_focus_start) {
mp_io_pipeline_focus();
}
}
static void
run_camera_switch_action(GSimpleAction *action, GVariant *param, gpointer user_data)
{
size_t next_index = camera->index + 1;
const struct mp_camera_config *next_camera =
mp_get_camera_config(next_index);
if (!next_camera) {
next_index = 0;
next_camera = mp_get_camera_config(next_index);
}
camera = next_camera;
update_io_pipeline();
}
static void
run_open_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data)
{
gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "settings");
}
static void
run_close_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data)
{
gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "main");
}
static void
on_controls_scale_changed(GtkAdjustment *adjustment, void (*set_fn)(double))
{
set_fn(gtk_adjustment_get_value(adjustment));
}
static void
update_value(GtkAdjustment *adjustment, GtkLabel *label)
{
char buf[12];
snprintf(buf, 12, "%.0f", gtk_adjustment_get_value(adjustment));
gtk_label_set_label(label, buf);
}
static void
on_auto_controls_toggled(GtkToggleButton *button, void (*set_auto_fn)(bool))
{
set_auto_fn(gtk_toggle_button_get_active(button));
}
static void
update_scale(GtkToggleButton *button, GtkScale *scale)
{
gtk_widget_set_sensitive(GTK_WIDGET(scale), !gtk_toggle_button_get_active(button));
}
static void
open_controls(GtkWidget *parent, const char *title_name,
double min_value, double max_value, double current,
bool auto_enabled,
void (*set_fn)(double),
void (*set_auto_fn)(bool))
{
GtkBuilder *builder = gtk_builder_new_from_resource(
"/org/postmarketos/Megapixels/controls-popover.ui");
GtkPopover *popover = GTK_POPOVER(gtk_builder_get_object(builder, "controls"));
GtkScale *scale = GTK_SCALE(gtk_builder_get_object(builder, "scale"));
GtkLabel *title = GTK_LABEL(gtk_builder_get_object(builder, "title"));
GtkLabel *value_label = GTK_LABEL(gtk_builder_get_object(builder, "value-label"));
GtkToggleButton *auto_button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "auto-button"));
gtk_label_set_label(title, title_name);
GtkAdjustment *adjustment = gtk_range_get_adjustment(GTK_RANGE(scale));
gtk_adjustment_set_lower(adjustment, min_value);
gtk_adjustment_set_upper(adjustment, max_value);
gtk_adjustment_set_value(adjustment, current);
update_value(adjustment, value_label);
gtk_toggle_button_set_active(auto_button, auto_enabled);
update_scale(auto_button, scale);
g_signal_connect(adjustment, "value-changed", G_CALLBACK(on_controls_scale_changed), set_fn);
g_signal_connect(adjustment, "value-changed", G_CALLBACK(update_value), value_label);
g_signal_connect(auto_button, "toggled", G_CALLBACK(on_auto_controls_toggled), set_auto_fn);
g_signal_connect(auto_button, "toggled", G_CALLBACK(update_scale), scale);
gtk_widget_set_parent(GTK_WIDGET(popover), parent);
gtk_popover_popup(popover);
// g_object_unref(popover);
}
static void
set_gain(double value)
{
if (gain != (int)value) {
gain = value;
update_io_pipeline();
}
}
static void
set_gain_auto(bool is_auto)
{
if (gain_is_manual != !is_auto) {
gain_is_manual = !is_auto;
update_io_pipeline();
}
}
static void
open_iso_controls(GtkWidget *button, gpointer user_data)
{
open_controls(button, "ISO", 0, gain_max, gain, !gain_is_manual, set_gain, set_gain_auto);
}
static void
set_shutter(double value)
{
int new_exposure =
(int)(value / 360.0 * camera->capture_mode.height);
if (new_exposure != exposure) {
exposure = new_exposure;
update_io_pipeline();
}
}
static void
set_shutter_auto(bool is_auto)
{
if (exposure_is_manual != !is_auto) {
exposure_is_manual = !is_auto;
update_io_pipeline();
}
}
static void
open_shutter_controls(GtkWidget *button, gpointer user_data)
{
open_controls(button, "Shutter", 1.0, 360.0, exposure, !exposure_is_manual, set_shutter, set_shutter_auto);
}
static void
on_realize(GtkWidget *window, gpointer *data)
{
GtkNative *native = gtk_widget_get_native(window);
mp_process_pipeline_init_gl(gtk_native_get_surface(native));
camera = mp_get_camera_config(0);
update_io_pipeline();
}
static GSimpleAction *
create_simple_action(GtkApplication *app, const char *name, GCallback callback)
{
GSimpleAction *action = g_simple_action_new(name, NULL);
g_signal_connect(action, "activate", callback, app);
g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(action));
return action;
}
static void update_ui_rotation()
{
if (device_rotation == 0 || device_rotation == 180) {
// Portrait
gtk_widget_set_halign(preview_top_box, GTK_ALIGN_FILL);
gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_top_box), GTK_ORIENTATION_VERTICAL);
gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_FILL);
gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_bottom_box), GTK_ORIENTATION_HORIZONTAL);
if (device_rotation == 0) {
gtk_widget_set_valign(preview_top_box, GTK_ALIGN_START);
gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_END);
} else {
gtk_widget_set_valign(preview_top_box, GTK_ALIGN_END);
gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_START);
}
} else {
// Landscape
gtk_widget_set_valign(preview_top_box, GTK_ALIGN_FILL);
gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_top_box), GTK_ORIENTATION_HORIZONTAL);
gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_FILL);
gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_bottom_box), GTK_ORIENTATION_VERTICAL);
if (device_rotation == 90) {
gtk_widget_set_halign(preview_top_box, GTK_ALIGN_END);
gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_START);
} else {
gtk_widget_set_halign(preview_top_box, GTK_ALIGN_START);
gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_END);
}
}
}
static void display_config_received(GDBusConnection *conn, GAsyncResult *res, gpointer user_data)
{
GError *error = NULL;
GVariant *result = g_dbus_connection_call_finish(conn, res, &error);
if (!result) {
printf("Failed to get display configuration: %s\n", error->message);
return;
}
GVariant *configs = g_variant_get_child_value(result, 1);
if (g_variant_n_children(configs) == 0) {
return;
}
GVariant *config = g_variant_get_child_value(configs, 0);
GVariant *rot_config = g_variant_get_child_value(config, 7);
uint32_t rotation_index = g_variant_get_uint32(rot_config);
assert(rotation_index < 4);
int new_rotation = rotation_index * 90;
if (new_rotation != device_rotation) {
device_rotation = new_rotation;
update_io_pipeline();
update_ui_rotation();
}
}
static void update_screen_rotation(GDBusConnection *conn)
{
g_dbus_connection_call(conn,
"org.gnome.Mutter.DisplayConfig",
"/org/gnome/Mutter/DisplayConfig",
"org.gnome.Mutter.DisplayConfig",
"GetResources",
NULL,
NULL,
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1,
NULL,
(GAsyncReadyCallback)display_config_received,
NULL);
}
static void on_screen_rotate(
GDBusConnection *conn,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
update_screen_rotation(conn);
}
static void
activate(GtkApplication *app, gpointer data)
{
g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme",
TRUE, NULL);
GdkDisplay *display = gdk_display_get_default();
GtkIconTheme *icon_theme = gtk_icon_theme_get_for_display(display);
gtk_icon_theme_add_resource_path(icon_theme, "/org/postmarketos/Megapixels");
GtkCssProvider *provider = gtk_css_provider_new();
gtk_css_provider_load_from_resource(
provider, "/org/postmarketos/Megapixels/camera.css");
gtk_style_context_add_provider_for_display(
display, GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
GtkBuilder *builder = gtk_builder_new_from_resource(
"/org/postmarketos/Megapixels/camera.ui");
GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
GtkWidget *iso_button = GTK_WIDGET(gtk_builder_get_object(builder, "iso-controls-button"));
GtkWidget *shutter_button = GTK_WIDGET(gtk_builder_get_object(builder, "shutter-controls-button"));
preview = GTK_WIDGET(gtk_builder_get_object(builder, "preview"));
main_stack = GTK_WIDGET(gtk_builder_get_object(builder, "main_stack"));
open_last_stack = GTK_WIDGET(gtk_builder_get_object(builder, "open_last_stack"));
thumb_last = GTK_WIDGET(gtk_builder_get_object(builder, "thumb_last"));
process_spinner = GTK_WIDGET(gtk_builder_get_object(builder, "process_spinner"));
scanned_codes = GTK_WIDGET(gtk_builder_get_object(builder, "scanned-codes"));
preview_top_box = GTK_WIDGET(gtk_builder_get_object(builder, "top-box"));
preview_bottom_box = GTK_WIDGET(gtk_builder_get_object(builder, "bottom-box"));
g_signal_connect(window, "realize", G_CALLBACK(on_realize), NULL);
g_signal_connect(preview, "realize", G_CALLBACK(preview_realize), NULL);
g_signal_connect(preview, "render", G_CALLBACK(preview_draw), NULL);
g_signal_connect(preview, "resize", G_CALLBACK(preview_resize), NULL);
GtkGesture *click = gtk_gesture_click_new();
g_signal_connect(click, "pressed", G_CALLBACK(preview_pressed), NULL);
gtk_widget_add_controller(preview, GTK_EVENT_CONTROLLER(click));
g_signal_connect(iso_button, "clicked", G_CALLBACK(open_iso_controls), NULL);
g_signal_connect(shutter_button, "clicked", G_CALLBACK(open_shutter_controls), NULL);
// Setup actions
create_simple_action(app, "capture", G_CALLBACK(run_capture_action));
create_simple_action(app, "switch-camera", G_CALLBACK(run_camera_switch_action));
create_simple_action(app, "open-settings", G_CALLBACK(run_open_settings_action));
create_simple_action(app, "close-settings", G_CALLBACK(run_close_settings_action));
create_simple_action(app, "open-last", G_CALLBACK(run_open_last_action));
create_simple_action(app, "open-photos", G_CALLBACK(run_open_photos_action));
create_simple_action(app, "quit", G_CALLBACK(run_quit_action));
// Setup shortcuts
const char *capture_accels[] = { "space", NULL };
gtk_application_set_accels_for_action(app, "app.capture", capture_accels);
const char *quit_accels[] = { "<Ctrl>q", "<Ctrl>w", NULL };
gtk_application_set_accels_for_action(app, "app.quit", quit_accels);
// Listen for phosh rotation
GDBusConnection *conn = g_application_get_dbus_connection(G_APPLICATION(app));
g_dbus_connection_signal_subscribe(
conn,
NULL,
"org.gnome.Mutter.DisplayConfig",
"MonitorsChanged",
"/org/gnome/Mutter/DisplayConfig",
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
&on_screen_rotate,
NULL,
NULL);
update_screen_rotation(conn);
mp_io_pipeline_start();
gtk_application_add_window(app, GTK_WINDOW(window));
gtk_widget_show(window);
}
static void
shutdown(GApplication *app, gpointer data)
{
// Only do cleanup in development, let the OS clean up otherwise
#ifdef DEBUG
mp_io_pipeline_stop();
#endif
}
int
main(int argc, char *argv[])
{
#ifdef RENDERDOC
{
void *mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
if (mod)
{
pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI");
int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void **)&rdoc_api);
assert(ret == 1);
}
else
{
printf("Renderdoc not found\n");
}
}
#endif
if (!mp_load_config())
return 1;
setenv("LC_NUMERIC", "C", 1);
GtkApplication *app = gtk_application_new("org.postmarketos.Megapixels", 0);
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
g_signal_connect(app, "shutdown", G_CALLBACK(shutdown), NULL);
g_application_run(G_APPLICATION(app), argc, argv);
return 0;
}

View File

@ -2,14 +2,16 @@
#include "camera_config.h"
#include "zbar_pipeline.h"
#include "process_pipeline.h"
#include "gtk/gtk.h"
#define MP_MAIN_THUMB_SIZE 24
struct mp_main_state {
const struct mp_camera_config *camera;
MPCameraMode mode;
int image_width;
int image_height;
bool gain_is_manual;
int gain;
int gain_max;
@ -23,12 +25,9 @@ struct mp_main_state {
void mp_main_update_state(const struct mp_main_state *state);
void mp_main_set_preview(cairo_surface_t *image);
void mp_main_capture_completed(cairo_surface_t *thumb, const char *fname);
void mp_main_set_preview(MPProcessPipelineBuffer *buffer);
void mp_main_capture_completed(GdkTexture *thumb, const char *fname);
void mp_main_set_zbar_result(MPZBarScanResult *result);
int remap(int value, int input_min, int input_max, int output_min, int output_max);
void draw_surface_scaled_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height,
cairo_surface_t *surface);

View File

@ -65,6 +65,27 @@ mp_pipeline_invoke(MPPipeline *pipeline, MPPipelineCallback callback,
}
}
static bool
unlock_mutex(GMutex *mutex)
{
g_mutex_unlock(mutex);
return false;
}
void
mp_pipeline_sync(MPPipeline *pipeline)
{
GMutex mutex;
g_mutex_init(&mutex);
g_mutex_lock(&mutex);
g_main_context_invoke_full(pipeline->main_context, G_PRIORITY_LOW, (GSourceFunc)unlock_mutex, &mutex, NULL);
g_mutex_lock(&mutex);
g_mutex_unlock(&mutex);
g_mutex_clear(&mutex);
}
void
mp_pipeline_free(MPPipeline *pipeline)
{
@ -80,21 +101,24 @@ mp_pipeline_free(MPPipeline *pipeline)
struct capture_source_args {
MPCamera *camera;
void (*callback)(MPImage, void *);
void (*callback)(MPBuffer, void *);
void *user_data;
};
static bool
on_capture(int fd, GIOCondition condition, struct capture_source_args *args)
{
mp_camera_capture_image(args->camera, args->callback, args->user_data);
MPBuffer buffer;
if (mp_camera_capture_buffer(args->camera, &buffer)) {
args->callback(buffer, args->user_data);
}
return true;
}
// Not thread safe
GSource *
mp_pipeline_add_capture_source(MPPipeline *pipeline, MPCamera *camera,
void (*callback)(MPImage, void *), void *user_data)
void (*callback)(MPBuffer, void *), void *user_data)
{
int video_fd = mp_camera_get_video_fd(camera);
GSource *video_source = g_unix_fd_source_new(video_fd, G_IO_IN);

View File

@ -11,8 +11,10 @@ typedef void (*MPPipelineCallback)(MPPipeline *, const void *);
MPPipeline *mp_pipeline_new();
void mp_pipeline_invoke(MPPipeline *pipeline, MPPipelineCallback callback,
const void *data, size_t size);
// Wait until all pending tasks have completed
void mp_pipeline_sync(MPPipeline *pipeline);
void mp_pipeline_free(MPPipeline *pipeline);
GSource *mp_pipeline_add_capture_source(MPPipeline *pipeline, MPCamera *camera,
void (*callback)(MPImage, void *),
void (*callback)(MPBuffer, void *),
void *user_data);

View File

@ -2,15 +2,18 @@
#include "pipeline.h"
#include "zbar_pipeline.h"
#include "io_pipeline.h"
#include "main.h"
#include "config.h"
#include "quickpreview.h"
#include "gles2_debayer.h"
#include <tiffio.h>
#include <assert.h>
#include <math.h>
#include <wordexp.h>
#include <gtk/gtk.h>
#include "gl_util.h"
#include <sys/mman.h>
#define TIFFTAG_FORWARDMATRIX1 50964
static const float colormatrix_srgb[] = { 3.2409, -1.5373, -0.4986, -0.9692, 1.8759,
@ -26,6 +29,7 @@ static volatile int frames_processed = 0;
static volatile int frames_received = 0;
static const struct mp_camera_config *camera;
static int camera_rotation;
static MPCameraMode mode;
@ -35,6 +39,11 @@ static int captures_remaining = 0;
static int preview_width;
static int preview_height;
static int device_rotation;
static int output_buffer_width = -1;
static int output_buffer_height = -1;
// static bool gain_is_manual;
static int gain;
static int gain_max;
@ -60,28 +69,18 @@ register_custom_tiff_tags(TIFF *tif)
static bool
find_processor(char *script)
{
char *xdg_config_home;
char filename[] = "postprocess.sh";
wordexp_t exp_result;
// Resolve XDG stuff
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) {
xdg_config_home = "~/.config";
}
wordexp(xdg_config_home, &exp_result, 0);
xdg_config_home = strdup(exp_result.we_wordv[0]);
wordfree(&exp_result);
// Check postprocess.h in the current working directory
sprintf(script, "%s", filename);
// Check postprocess.sh in the current working directory
sprintf(script, "./data/%s", filename);
if (access(script, F_OK) != -1) {
sprintf(script, "./%s", filename);
sprintf(script, "./data/%s", filename);
printf("Found postprocessor script at %s\n", script);
return true;
}
// Check for a script in XDG_CONFIG_HOME
sprintf(script, "%s/megapixels/%s", xdg_config_home, filename);
sprintf(script, "%s/megapixels/%s", g_get_user_config_dir(), filename);
if (access(script, F_OK) != -1) {
printf("Found postprocessor script at %s\n", script);
return true;
@ -134,47 +133,208 @@ mp_process_pipeline_stop()
mp_zbar_pipeline_stop();
}
static cairo_surface_t *
process_image_for_preview(const MPImage *image)
void
mp_process_pipeline_sync()
{
uint32_t surface_width, surface_height, skip;
quick_preview_size(&surface_width, &surface_height, &skip, preview_width,
preview_height, image->width, image->height,
image->pixel_format, camera->rotate);
mp_pipeline_sync(pipeline);
}
cairo_surface_t *surface = cairo_image_surface_create(
CAIRO_FORMAT_RGB24, surface_width, surface_height);
#define NUM_BUFFERS 4
uint8_t *pixels = cairo_image_surface_get_data(surface);
struct _MPProcessPipelineBuffer {
GLuint texture_id;
quick_preview((uint32_t *)pixels, surface_width, surface_height, image->data,
image->width, image->height, image->pixel_format,
camera->rotate, camera->mirrored,
camera->previewmatrix[0] == 0 ? NULL : camera->previewmatrix,
camera->blacklevel, skip);
_Atomic(int) refcount;
};
static MPProcessPipelineBuffer output_buffers[NUM_BUFFERS];
// Create a thumbnail from the preview for the last capture
cairo_surface_t *thumb = NULL;
if (captures_remaining == 1) {
printf("Making thumbnail\n");
thumb = cairo_image_surface_create(
CAIRO_FORMAT_ARGB32, MP_MAIN_THUMB_SIZE, MP_MAIN_THUMB_SIZE);
void
mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf)
{
++buf->refcount;
}
cairo_t *cr = cairo_create(thumb);
draw_surface_scaled_centered(
cr, MP_MAIN_THUMB_SIZE, MP_MAIN_THUMB_SIZE, surface);
cairo_destroy(cr);
void
mp_process_pipeline_buffer_unref(MPProcessPipelineBuffer *buf)
{
--buf->refcount;
}
uint32_t
mp_process_pipeline_buffer_get_texture_id(MPProcessPipelineBuffer *buf)
{
return buf->texture_id;
}
static GLES2Debayer *gles2_debayer = NULL;
static GdkGLContext *context;
#define RENDERDOC
#ifdef RENDERDOC
#include <renderdoc/app.h>
extern RENDERDOC_API_1_1_2 *rdoc_api;
#endif
static void
init_gl(MPPipeline *pipeline, GdkSurface **surface)
{
GError *error = NULL;
context = gdk_surface_create_gl_context(*surface, &error);
if (context == NULL) {
printf("Failed to initialize OpenGL context: %s\n", error->message);
g_clear_error(&error);
return;
}
// Pass processed preview to main and zbar
mp_zbar_pipeline_process_image(cairo_surface_reference(surface));
mp_main_set_preview(surface);
gdk_gl_context_set_use_es(context, true);
gdk_gl_context_set_required_version(context, 2, 0);
gdk_gl_context_set_forward_compatible(context, false);
#ifdef DEBUG
gdk_gl_context_set_debug_enabled(context, true);
#else
gdk_gl_context_set_debug_enabled(context, false);
#endif
gdk_gl_context_realize(context, &error);
if (error != NULL) {
printf("Failed to create OpenGL context: %s\n", error->message);
g_clear_object(&context);
g_clear_error(&error);
return;
}
gdk_gl_context_make_current(context);
check_gl();
// Make a VAO for OpenGL
if (!gdk_gl_context_get_use_es(context)) {
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
check_gl();
}
gles2_debayer = gles2_debayer_new(MP_PIXEL_FMT_BGGR8);
check_gl();
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
check_gl();
gles2_debayer_use(gles2_debayer);
for (size_t i = 0; i < NUM_BUFFERS; ++i) {
glGenTextures(1, &output_buffers[i].texture_id);
glBindTexture(GL_TEXTURE_2D, output_buffers[i].texture_id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
glBindTexture(GL_TEXTURE_2D, 0);
gboolean is_es = gdk_gl_context_get_use_es(context);
int major, minor;
gdk_gl_context_get_version(context, &major, &minor);
printf("Initialized %s %d.%d\n", is_es ? "OpenGL ES" : "OpenGL", major, minor);
}
void
mp_process_pipeline_init_gl(GdkSurface *surface)
{
mp_pipeline_invoke(pipeline, (MPPipelineCallback) init_gl, &surface, sizeof(GdkSurface *));
}
static GdkTexture *
process_image_for_preview(const uint8_t *image)
{
#ifdef PROFILE_DEBAYER
clock_t t1 = clock();
#endif
// Pick an available buffer
MPProcessPipelineBuffer *output_buffer = NULL;
for (size_t i = 0; i < NUM_BUFFERS; ++i) {
if (output_buffers[i].refcount == 0) {
output_buffer = &output_buffers[i];
}
}
if (output_buffer == NULL) {
return NULL;
}
assert(output_buffer != NULL);
#ifdef RENDERDOC
if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL);
#endif
// Copy image to a GL texture. TODO: This can be avoided
GLuint input_texture;
glGenTextures(1, &input_texture);
glBindTexture(GL_TEXTURE_2D, input_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, mode.width, mode.height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image);
check_gl();
gles2_debayer_process(
gles2_debayer, output_buffer->texture_id, input_texture);
check_gl();
glFinish();
glDeleteTextures(1, &input_texture);
#ifdef PROFILE_DEBAYER
clock_t t2 = clock();
printf("process_image_for_preview %fms\n", (float)(t2 - t1) / CLOCKS_PER_SEC * 1000);
#endif
#ifdef RENDERDOC
if(rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL);
#endif
mp_process_pipeline_buffer_ref(output_buffer);
mp_main_set_preview(output_buffer);
// Create a thumbnail from the preview for the last capture
GdkTexture *thumb = NULL;
if (captures_remaining == 1) {
printf("Making thumbnail\n");
size_t size = output_buffer_width * output_buffer_height * sizeof(uint32_t);
uint32_t *data = g_malloc_n(size, 1);
glReadPixels(0, 0, output_buffer_width, output_buffer_height, GL_RGBA, GL_UNSIGNED_BYTE, data);
check_gl();
// Flip vertically
for (size_t y = 0; y < output_buffer_height / 2; ++y) {
for (size_t x = 0; x < output_buffer_width; ++x) {
uint32_t tmp = data[(output_buffer_height - y - 1) * output_buffer_width + x];
data[(output_buffer_height - y - 1) * output_buffer_width + x] = data[y * output_buffer_width + x];
data[y * output_buffer_width + x] = tmp;
}
}
thumb = gdk_memory_texture_new(
output_buffer_width,
output_buffer_height,
GDK_MEMORY_R8G8B8A8,
g_bytes_new_take(data, size),
output_buffer_width * sizeof(uint32_t));
}
return thumb;
}
static void
process_image_for_capture(const MPImage *image, int count)
process_image_for_capture(const uint8_t *image, int count)
{
time_t rawtime;
time(&rawtime);
@ -193,21 +353,21 @@ process_image_for_capture(const MPImage *image, int count)
// Define TIFF thumbnail
TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 1);
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, image->width >> 4);
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, image->height >> 4);
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, mode.width >> 4);
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, mode.height >> 4);
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
TIFFSetField(tif, TIFFTAG_MAKE, mp_get_device_make());
TIFFSetField(tif, TIFFTAG_MODEL, mp_get_device_model());
uint16_t orientation;
if (camera->rotate == 0) {
if (camera_rotation == 0) {
orientation = camera->mirrored ? ORIENTATION_TOPRIGHT :
ORIENTATION_TOPLEFT;
} else if (camera->rotate == 90) {
} else if (camera_rotation == 90) {
orientation = camera->mirrored ? ORIENTATION_RIGHTBOT :
ORIENTATION_LEFTBOT;
} else if (camera->rotate == 180) {
} else if (camera_rotation == 180) {
orientation = camera->mirrored ? ORIENTATION_BOTLEFT :
ORIENTATION_BOTRIGHT;
} else {
@ -241,8 +401,8 @@ process_image_for_capture(const MPImage *image, int count)
// Write black thumbnail, only windows uses this
{
unsigned char *buf =
(unsigned char *)calloc(1, (int)image->width >> 4);
for (int row = 0; row < (image->height >> 4); row++) {
(unsigned char *)calloc(1, (int)mode.width >> 4);
for (int row = 0; row < (mode.height >> 4); row++) {
TIFFWriteScanline(tif, buf, row, 0);
}
free(buf);
@ -251,8 +411,8 @@ process_image_for_capture(const MPImage *image, int count)
// Define main photo
TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0);
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, image->width);
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, image->height);
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, mode.width);
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, mode.height);
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA);
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);
@ -274,9 +434,9 @@ process_image_for_capture(const MPImage *image, int count)
TIFFCheckpointDirectory(tif);
printf("Writing frame to %s\n", fname);
unsigned char *pLine = (unsigned char *)malloc(image->width);
for (int row = 0; row < image->height; row++) {
TIFFWriteScanline(tif, image->data + (row * image->width), row, 0);
unsigned char *pLine = (unsigned char *)malloc(mode.width);
for (int row = 0; row < mode.height; row++) {
TIFFWriteScanline(tif, (void *) image + (row * mode.width), row, 0);
}
free(pLine);
TIFFWriteDirectory(tif);
@ -293,7 +453,7 @@ process_image_for_capture(const MPImage *image, int count)
TIFFSetField(tif, EXIFTAG_EXPOSURETIME,
(mode.frame_interval.numerator /
(float)mode.frame_interval.denominator) /
((float)image->height / (float)exposure));
((float)mode.height / (float)exposure));
uint16_t isospeed[1];
isospeed[0] = (uint16_t)remap(gain - 1, 0, gain_max, camera->iso_min,
camera->iso_max);
@ -325,7 +485,7 @@ process_image_for_capture(const MPImage *image, int count)
}
static void
post_process_finished(GSubprocess *proc, GAsyncResult *res, cairo_surface_t *thumb)
post_process_finished(GSubprocess *proc, GAsyncResult *res, GdkTexture *thumb)
{
char *stdout;
g_subprocess_communicate_utf8_finish(proc, res, &stdout, NULL, NULL);
@ -348,7 +508,7 @@ post_process_finished(GSubprocess *proc, GAsyncResult *res, cairo_surface_t *thu
}
static void
process_capture_burst(cairo_surface_t *thumb)
process_capture_burst(GdkTexture *thumb)
{
time_t rawtime;
time(&rawtime);
@ -401,11 +561,27 @@ process_capture_burst(cairo_surface_t *thumb)
}
static void
process_image(MPPipeline *pipeline, const MPImage *image)
process_image(MPPipeline *pipeline, const MPBuffer *buffer)
{
assert(image->width == mode.width && image->height == mode.height);
#ifdef PROFILE_PROCESS
clock_t t1 = clock();
#endif
cairo_surface_t *thumb = process_image_for_preview(image);
size_t size =
mp_pixel_format_width_to_bytes(mode.pixel_format, mode.width) *
mode.height;
uint8_t *image = malloc(size);
memcpy(image, buffer->data, size);
mp_io_pipeline_release_buffer(buffer->index);
MPZBarImage *zbar_image = mp_zbar_image_new(image, mode.pixel_format, mode.width, mode.height, camera_rotation, camera->mirrored);
mp_zbar_pipeline_process_image(mp_zbar_image_ref(zbar_image));
#ifdef PROFILE_PROCESS
clock_t t2 = clock();
#endif
GdkTexture *thumb = process_image_for_preview(image);
if (captures_remaining > 0) {
int count = burst_length - captures_remaining;
@ -423,28 +599,35 @@ process_image(MPPipeline *pipeline, const MPImage *image)
assert(!thumb);
}
free(image->data);
mp_zbar_image_unref(zbar_image);
++frames_processed;
if (captures_remaining == 0) {
is_capturing = false;
}
#ifdef PROFILE_PROCESS
clock_t t3 = clock();
printf("process_image %fms, step 1:%fms, step 2:%fms\n",
(float)(t3 - t1) / CLOCKS_PER_SEC * 1000,
(float)(t2 - t1) / CLOCKS_PER_SEC * 1000,
(float)(t3 - t2) / CLOCKS_PER_SEC * 1000);
#endif
}
void
mp_process_pipeline_process_image(MPImage image)
mp_process_pipeline_process_image(MPBuffer buffer)
{
// If we haven't processed the previous frame yet, drop this one
if (frames_received != frames_processed && !is_capturing) {
printf("Dropped frame at capture\n");
free(image.data);
mp_io_pipeline_release_buffer(buffer.index);
return;
}
++frames_received;
mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &image,
sizeof(MPImage));
mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &buffer,
sizeof(MPBuffer));
}
static void
@ -472,17 +655,60 @@ mp_process_pipeline_capture()
mp_pipeline_invoke(pipeline, capture, NULL, 0);
}
static void
on_output_changed()
{
output_buffer_width = mode.width / 2;
output_buffer_height = mode.height / 2;
if (camera->rotate != 0 || camera->rotate != 180) {
int tmp = output_buffer_width;
output_buffer_width = output_buffer_height;
output_buffer_height = tmp;
}
for (size_t i = 0; i < NUM_BUFFERS; ++i) {
glBindTexture(GL_TEXTURE_2D, output_buffers[i].texture_id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, output_buffer_width, output_buffer_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
}
glBindTexture(GL_TEXTURE_2D, 0);
gles2_debayer_configure(
gles2_debayer,
output_buffer_width, output_buffer_height,
mode.width, mode.height,
camera->rotate, camera->mirrored,
camera->previewmatrix[0] == 0 ? NULL : camera->previewmatrix,
camera->blacklevel);
}
static int
mod(int a, int b)
{
int r = a % b;
return r < 0 ? r + b : r;
}
static void
update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state)
{
const bool output_changed =
!mp_camera_mode_is_equivalent(&mode, &state->mode)
|| preview_width != state->preview_width
|| preview_height != state->preview_height
|| device_rotation != state->device_rotation;
camera = state->camera;
mode = state->mode;
burst_length = state->burst_length;
preview_width = state->preview_width;
preview_height = state->preview_height;
device_rotation = state->device_rotation;
burst_length = state->burst_length;
// gain_is_manual = state->gain_is_manual;
gain = state->gain;
gain_max = state->gain_max;
@ -490,9 +716,17 @@ update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state
exposure_is_manual = state->exposure_is_manual;
exposure = state->exposure;
if (output_changed) {
camera_rotation = mod(camera->rotate - device_rotation, 360);
on_output_changed();
}
struct mp_main_state main_state = {
.camera = camera,
.mode = mode,
.image_width = output_buffer_width,
.image_height = output_buffer_height,
.gain_is_manual = state->gain_is_manual,
.gain = gain,
.gain_max = gain_max,
@ -510,3 +744,6 @@ mp_process_pipeline_update_state(const struct mp_process_pipeline_state *new_sta
mp_pipeline_invoke(pipeline, (MPPipelineCallback)update_state, new_state,
sizeof(struct mp_process_pipeline_state));
}
// GTK4 seems to require this
void pango_fc_font_get_languages() {}

View File

@ -2,6 +2,8 @@
#include "camera_config.h"
typedef struct _GdkSurface GdkSurface;
struct mp_process_pipeline_state {
const struct mp_camera_config *camera;
MPCameraMode mode;
@ -11,6 +13,8 @@ struct mp_process_pipeline_state {
int preview_width;
int preview_height;
int device_rotation;
bool gain_is_manual;
int gain;
int gain_max;
@ -24,7 +28,16 @@ struct mp_process_pipeline_state {
void mp_process_pipeline_start();
void mp_process_pipeline_stop();
void mp_process_pipeline_sync();
void mp_process_pipeline_process_image(MPImage image);
void mp_process_pipeline_init_gl(GdkSurface *window);
void mp_process_pipeline_process_image(MPBuffer buffer);
void mp_process_pipeline_capture();
void mp_process_pipeline_update_state(const struct mp_process_pipeline_state *state);
typedef struct _MPProcessPipelineBuffer MPProcessPipelineBuffer;
void mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf);
void mp_process_pipeline_buffer_unref(MPProcessPipelineBuffer *buf);
uint32_t mp_process_pipeline_buffer_get_texture_id(MPProcessPipelineBuffer *buf);

688
src/renderdoc/app.h Normal file
View File

@ -0,0 +1,688 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2021 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#pragma once
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html
//
#if !defined(RENDERDOC_NO_STDINT)
#include <stdint.h>
#endif
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
#define RENDERDOC_CC __cdecl
#elif defined(__linux__)
#define RENDERDOC_CC
#elif defined(__APPLE__)
#define RENDERDOC_CC
#else
#error "Unknown platform"
#endif
#ifdef __cplusplus
extern "C" {
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////
// Constants not used directly in below API
// This is a GUID/magic value used for when applications pass a path where shader debug
// information can be found to match up with a stripped shader.
// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue =
// RENDERDOC_ShaderDebugMagicValue_value
#define RENDERDOC_ShaderDebugMagicValue_struct \
{ \
0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
}
// as an alternative when you want a byte array (assuming x86 endianness):
#define RENDERDOC_ShaderDebugMagicValue_bytearray \
{ \
0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
}
// truncated version when only a uint64_t is available (e.g. Vulkan tags):
#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL
//////////////////////////////////////////////////////////////////////////////////////////////////
// RenderDoc capture options
//
typedef enum RENDERDOC_CaptureOption {
// Allow the application to enable vsync
//
// Default - enabled
//
// 1 - The application can enable or disable vsync at will
// 0 - vsync is force disabled
eRENDERDOC_Option_AllowVSync = 0,
// Allow the application to enable fullscreen
//
// Default - enabled
//
// 1 - The application can enable or disable fullscreen at will
// 0 - fullscreen is force disabled
eRENDERDOC_Option_AllowFullscreen = 1,
// Record API debugging events and messages
//
// Default - disabled
//
// 1 - Enable built-in API debugging features and records the results into
// the capture, which is matched up with events on replay
// 0 - no API debugging is forcibly enabled
eRENDERDOC_Option_APIValidation = 2,
eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum
// Capture CPU callstacks for API events
//
// Default - disabled
//
// 1 - Enables capturing of callstacks
// 0 - no callstacks are captured
eRENDERDOC_Option_CaptureCallstacks = 3,
// When capturing CPU callstacks, only capture them from drawcalls.
// This option does nothing without the above option being enabled
//
// Default - disabled
//
// 1 - Only captures callstacks for drawcall type API events.
// Ignored if CaptureCallstacks is disabled
// 0 - Callstacks, if enabled, are captured for every event.
eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4,
// Specify a delay in seconds to wait for a debugger to attach, after
// creating or injecting into a process, before continuing to allow it to run.
//
// 0 indicates no delay, and the process will run immediately after injection
//
// Default - 0 seconds
//
eRENDERDOC_Option_DelayForDebugger = 5,
// Verify buffer access. This includes checking the memory returned by a Map() call to
// detect any out-of-bounds modification, as well as initialising buffers with undefined contents
// to a marker value to catch use of uninitialised memory.
//
// NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
// not do the same kind of interception & checking and undefined contents are really undefined.
//
// Default - disabled
//
// 1 - Verify buffer access
// 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in
// RenderDoc.
eRENDERDOC_Option_VerifyBufferAccess = 6,
// The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites.
// This option now controls the filling of uninitialised buffers with 0xdddddddd which was
// previously always enabled
eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess,
// Hooks any system API calls that create child processes, and injects
// RenderDoc into them recursively with the same options.
//
// Default - disabled
//
// 1 - Hooks into spawned child processes
// 0 - Child processes are not hooked by RenderDoc
eRENDERDOC_Option_HookIntoChildren = 7,
// By default RenderDoc only includes resources in the final capture necessary
// for that frame, this allows you to override that behaviour.
//
// Default - disabled
//
// 1 - all live resources at the time of capture are included in the capture
// and available for inspection
// 0 - only the resources referenced by the captured frame are included
eRENDERDOC_Option_RefAllResources = 8,
// **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
// getting it will be ignored, to allow compatibility with older versions.
// In v1.1 the option acts as if it's always enabled.
//
// By default RenderDoc skips saving initial states for resources where the
// previous contents don't appear to be used, assuming that writes before
// reads indicate previous contents aren't used.
//
// Default - disabled
//
// 1 - initial contents at the start of each captured frame are saved, even if
// they are later overwritten or cleared before being used.
// 0 - unless a read is detected, initial contents will not be saved and will
// appear as black or empty data.
eRENDERDOC_Option_SaveAllInitials = 9,
// In APIs that allow for the recording of command lists to be replayed later,
// RenderDoc may choose to not capture command lists before a frame capture is
// triggered, to reduce overheads. This means any command lists recorded once
// and replayed many times will not be available and may cause a failure to
// capture.
//
// NOTE: This is only true for APIs where multithreading is difficult or
// discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
// and always capture all command lists since the API is heavily oriented
// around it and the overheads have been reduced by API design.
//
// 1 - All command lists are captured from the start of the application
// 0 - Command lists are only captured if their recording begins during
// the period when a frame capture is in progress.
eRENDERDOC_Option_CaptureAllCmdLists = 10,
// Mute API debugging output when the API validation mode option is enabled
//
// Default - enabled
//
// 1 - Mute any API debug messages from being displayed or passed through
// 0 - API debugging is displayed as normal
eRENDERDOC_Option_DebugOutputMute = 11,
// Option to allow vendor extensions to be used even when they may be
// incompatible with RenderDoc and cause corrupted replays or crashes.
//
// Default - inactive
//
// No values are documented, this option should only be used when absolutely
// necessary as directed by a RenderDoc developer.
eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12,
} RENDERDOC_CaptureOption;
// Sets an option that controls how RenderDoc behaves on capture.
//
// Returns 1 if the option and value are valid
// Returns 0 if either is invalid and the option is unchanged
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val);
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val);
// Gets the current value of an option as a uint32_t
//
// If the option is invalid, 0xffffffff is returned
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt);
// Gets the current value of an option as a float
//
// If the option is invalid, -FLT_MAX is returned
typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt);
typedef enum RENDERDOC_InputButton {
// '0' - '9' matches ASCII values
eRENDERDOC_Key_0 = 0x30,
eRENDERDOC_Key_1 = 0x31,
eRENDERDOC_Key_2 = 0x32,
eRENDERDOC_Key_3 = 0x33,
eRENDERDOC_Key_4 = 0x34,
eRENDERDOC_Key_5 = 0x35,
eRENDERDOC_Key_6 = 0x36,
eRENDERDOC_Key_7 = 0x37,
eRENDERDOC_Key_8 = 0x38,
eRENDERDOC_Key_9 = 0x39,
// 'A' - 'Z' matches ASCII values
eRENDERDOC_Key_A = 0x41,
eRENDERDOC_Key_B = 0x42,
eRENDERDOC_Key_C = 0x43,
eRENDERDOC_Key_D = 0x44,
eRENDERDOC_Key_E = 0x45,
eRENDERDOC_Key_F = 0x46,
eRENDERDOC_Key_G = 0x47,
eRENDERDOC_Key_H = 0x48,
eRENDERDOC_Key_I = 0x49,
eRENDERDOC_Key_J = 0x4A,
eRENDERDOC_Key_K = 0x4B,
eRENDERDOC_Key_L = 0x4C,
eRENDERDOC_Key_M = 0x4D,
eRENDERDOC_Key_N = 0x4E,
eRENDERDOC_Key_O = 0x4F,
eRENDERDOC_Key_P = 0x50,
eRENDERDOC_Key_Q = 0x51,
eRENDERDOC_Key_R = 0x52,
eRENDERDOC_Key_S = 0x53,
eRENDERDOC_Key_T = 0x54,
eRENDERDOC_Key_U = 0x55,
eRENDERDOC_Key_V = 0x56,
eRENDERDOC_Key_W = 0x57,
eRENDERDOC_Key_X = 0x58,
eRENDERDOC_Key_Y = 0x59,
eRENDERDOC_Key_Z = 0x5A,
// leave the rest of the ASCII range free
// in case we want to use it later
eRENDERDOC_Key_NonPrintable = 0x100,
eRENDERDOC_Key_Divide,
eRENDERDOC_Key_Multiply,
eRENDERDOC_Key_Subtract,
eRENDERDOC_Key_Plus,
eRENDERDOC_Key_F1,
eRENDERDOC_Key_F2,
eRENDERDOC_Key_F3,
eRENDERDOC_Key_F4,
eRENDERDOC_Key_F5,
eRENDERDOC_Key_F6,
eRENDERDOC_Key_F7,
eRENDERDOC_Key_F8,
eRENDERDOC_Key_F9,
eRENDERDOC_Key_F10,
eRENDERDOC_Key_F11,
eRENDERDOC_Key_F12,
eRENDERDOC_Key_Home,
eRENDERDOC_Key_End,
eRENDERDOC_Key_Insert,
eRENDERDOC_Key_Delete,
eRENDERDOC_Key_PageUp,
eRENDERDOC_Key_PageDn,
eRENDERDOC_Key_Backspace,
eRENDERDOC_Key_Tab,
eRENDERDOC_Key_PrtScrn,
eRENDERDOC_Key_Pause,
eRENDERDOC_Key_Max,
} RENDERDOC_InputButton;
// Sets which key or keys can be used to toggle focus between multiple windows
//
// If keys is NULL or num is 0, toggle keys will be disabled
typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num);
// Sets which key or keys can be used to capture the next frame
//
// If keys is NULL or num is 0, captures keys will be disabled
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num);
typedef enum RENDERDOC_OverlayBits {
// This single bit controls whether the overlay is enabled or disabled globally
eRENDERDOC_Overlay_Enabled = 0x1,
// Show the average framerate over several seconds as well as min/max
eRENDERDOC_Overlay_FrameRate = 0x2,
// Show the current frame number
eRENDERDOC_Overlay_FrameNumber = 0x4,
// Show a list of recent captures, and how many captures have been made
eRENDERDOC_Overlay_CaptureList = 0x8,
// Default values for the overlay mask
eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate |
eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList),
// Enable all bits
eRENDERDOC_Overlay_All = ~0U,
// Disable all bits
eRENDERDOC_Overlay_None = 0,
} RENDERDOC_OverlayBits;
// returns the overlay bits that have been set
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)();
// sets the overlay bits with an and & or mask
typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or);
// this function will attempt to remove RenderDoc's hooks in the application.
//
// Note: that this can only work correctly if done immediately after
// the module is loaded, before any API work happens. RenderDoc will remove its
// injected hooks and shut down. Behaviour is undefined if this is called
// after any API functions have been called, and there is still no guarantee of
// success.
typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)();
// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers.
typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown;
// This function will unload RenderDoc's crash handler.
//
// If you use your own crash handler and don't want RenderDoc's handler to
// intercede, you can call this function to unload it and any unhandled
// exceptions will pass to the next handler.
typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)();
// Sets the capture file path template
//
// pathtemplate is a UTF-8 string that gives a template for how captures will be named
// and where they will be saved.
//
// Any extension is stripped off the path, and captures are saved in the directory
// specified, and named with the filename and the frame number appended. If the
// directory does not exist it will be created, including any parent directories.
//
// If pathtemplate is NULL, the template will remain unchanged
//
// Example:
//
// SetCaptureFilePathTemplate("my_captures/example");
//
// Capture #1 -> my_captures/example_frame123.rdc
// Capture #2 -> my_captures/example_frame456.rdc
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate);
// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string
typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)();
// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers.
typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate;
typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate;
// returns the number of captures that have been made
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)();
// This function returns the details of a capture, by index. New captures are added
// to the end of the list.
//
// filename will be filled with the absolute path to the capture file, as a UTF-8 string
// pathlength will be written with the length in bytes of the filename string
// timestamp will be written with the time of the capture, in seconds since the Unix epoch
//
// Any of the parameters can be NULL and they'll be skipped.
//
// The function will return 1 if the capture index is valid, or 0 if the index is invalid
// If the index is invalid, the values will be unchanged
//
// Note: when captures are deleted in the UI they will remain in this list, so the
// capture path may not exist anymore.
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename,
uint32_t *pathlength, uint64_t *timestamp);
// Sets the comments associated with a capture file. These comments are displayed in the
// UI program when opening.
//
// filePath should be a path to the capture file to add comments to. If set to NULL or ""
// the most recent capture file created made will be used instead.
// comments should be a NULL-terminated UTF-8 string to add as comments.
//
// Any existing comments will be overwritten.
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath,
const char *comments);
// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)();
// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers.
// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for
// backwards compatibility with old code, it is castable either way since it's ABI compatible
// as the same function pointer type.
typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected;
// This function will launch the Replay UI associated with the RenderDoc library injected
// into the running application.
//
// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter
// to connect to this application
// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open
// if cmdline is NULL, the command line will be empty.
//
// returns the PID of the replay UI if successful, 0 if not successful.
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl,
const char *cmdline);
// RenderDoc can return a higher version than requested if it's backwards compatible,
// this function returns the actual version returned. If a parameter is NULL, it will be
// ignored and the others will be filled out.
typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch);
//////////////////////////////////////////////////////////////////////////
// Capturing functions
//
// A device pointer is a pointer to the API's root handle.
//
// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc
typedef void *RENDERDOC_DevicePointer;
// A window handle is the OS's native window handle
//
// This would be an HWND, GLXDrawable, etc
typedef void *RENDERDOC_WindowHandle;
// A helper macro for Vulkan, where the device handle cannot be used directly.
//
// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use.
//
// Specifically, the value needed is the dispatch table pointer, which sits as the first
// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and
// indirect once.
#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst)))
// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will
// respond to keypresses. Neither parameter can be NULL
typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device,
RENDERDOC_WindowHandle wndHandle);
// capture the next frame on whichever window and API is currently considered active
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)();
// capture the next N frames on whichever window and API is currently considered active
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames);
// When choosing either a device pointer or a window handle to capture, you can pass NULL.
// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify
// any API rendering to a specific window, or a specific API instance rendering to any window,
// or in the simplest case of one window and one API, you can just pass NULL for both.
//
// In either case, if there are two or more possible matching (device,window) pairs it
// is undefined which one will be captured.
//
// Note: for headless rendering you can pass NULL for the window handle and either specify
// a device pointer or leave it NULL as above.
// Immediately starts capturing API calls on the specified device pointer and window handle.
//
// If there is no matching thing to capture (e.g. no supported API has been initialised),
// this will do nothing.
//
// The results are undefined (including crashes) if two captures are started overlapping,
// even on separate devices and/oror windows.
typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device,
RENDERDOC_WindowHandle wndHandle);
// Returns whether or not a frame capture is currently ongoing anywhere.
//
// This will return 1 if a capture is ongoing, and 0 if there is no capture running
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)();
// Ends capturing immediately.
//
// This will return 1 if the capture succeeded, and 0 if there was an error capturing.
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device,
RENDERDOC_WindowHandle wndHandle);
// Ends capturing immediately and discard any data stored without saving to disk.
//
// This will return 1 if the capture was discarded, and 0 if there was an error or no capture
// was in progress
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device,
RENDERDOC_WindowHandle wndHandle);
//////////////////////////////////////////////////////////////////////////////////////////////////
// RenderDoc API versions
//
// RenderDoc uses semantic versioning (http://semver.org/).
//
// MAJOR version is incremented when incompatible API changes happen.
// MINOR version is incremented when functionality is added in a backwards-compatible manner.
// PATCH version is incremented when backwards-compatible bug fixes happen.
//
// Note that this means the API returned can be higher than the one you might have requested.
// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned
// instead of 1.0.0. You can check this with the GetAPIVersion entry point
typedef enum RENDERDOC_Version {
eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00
eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01
eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02
eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00
eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01
eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02
eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00
eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00
eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00
eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01
} RENDERDOC_Version;
// API version changelog:
//
// 1.0.0 - initial release
// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered
// by keypress or TriggerCapture, instead of Start/EndFrameCapture.
// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation
// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new
// function pointer is added to the end of the struct, the original layout is identical
// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote
// replay/remote server concept in replay UI)
// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these
// are captures and not debug logging files. This is the first API version in the v1.0
// branch.
// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be
// displayed in the UI program on load.
// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions
// which allows users to opt-in to allowing unsupported vendor extensions to function.
// Should be used at the user's own risk.
// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to
// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to
// 0xdddddddd of uninitialised buffer contents.
// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop
// capturing without saving anything to disk.
// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening
typedef struct RENDERDOC_API_1_4_1
{
pRENDERDOC_GetAPIVersion GetAPIVersion;
pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32;
pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32;
pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32;
pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32;
pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys;
pRENDERDOC_SetCaptureKeys SetCaptureKeys;
pRENDERDOC_GetOverlayBits GetOverlayBits;
pRENDERDOC_MaskOverlayBits MaskOverlayBits;
// Shutdown was renamed to RemoveHooks in 1.4.1.
// These unions allow old code to continue compiling without changes
union
{
pRENDERDOC_Shutdown Shutdown;
pRENDERDOC_RemoveHooks RemoveHooks;
};
pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
// Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2.
// These unions allow old code to continue compiling without changes
union
{
// deprecated name
pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate;
// current name
pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
};
union
{
// deprecated name
pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate;
// current name
pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
};
pRENDERDOC_GetNumCaptures GetNumCaptures;
pRENDERDOC_GetCapture GetCapture;
pRENDERDOC_TriggerCapture TriggerCapture;
// IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1.
// This union allows old code to continue compiling without changes
union
{
// deprecated name
pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected;
// current name
pRENDERDOC_IsTargetControlConnected IsTargetControlConnected;
};
pRENDERDOC_LaunchReplayUI LaunchReplayUI;
pRENDERDOC_SetActiveWindow SetActiveWindow;
pRENDERDOC_StartFrameCapture StartFrameCapture;
pRENDERDOC_IsFrameCapturing IsFrameCapturing;
pRENDERDOC_EndFrameCapture EndFrameCapture;
// new function in 1.1.0
pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture;
// new function in 1.2.0
pRENDERDOC_SetCaptureFileComments SetCaptureFileComments;
// new function in 1.4.0
pRENDERDOC_DiscardFrameCapture DiscardFrameCapture;
} RENDERDOC_API_1_4_1;
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_0;
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_1;
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_2;
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_0;
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_1;
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_2;
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_2_0;
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_3_0;
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_4_0;
//////////////////////////////////////////////////////////////////////////////////////////////////
// RenderDoc API entry point
//
// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available.
//
// The name is the same as the typedef - "RENDERDOC_GetAPI"
//
// This function is not thread safe, and should not be called on multiple threads at once.
// Ideally, call this once as early as possible in your application's startup, before doing
// any API work, since some configuration functionality etc has to be done also before
// initialising any APIs.
//
// Parameters:
// version is a single value from the RENDERDOC_Version above.
//
// outAPIPointers will be filled out with a pointer to the corresponding struct of function
// pointers.
//
// Returns:
// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested
// 0 - if the requested version is not supported or the arguments are invalid.
//
typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -6,6 +6,17 @@
#include <zbar.h>
#include <assert.h>
struct _MPZBarImage {
uint8_t *data;
MPPixelFormat pixel_format;
int width;
int height;
int rotation;
bool mirrored;
_Atomic int ref_count;
};
static MPPipeline *pipeline;
static volatile int frames_processed = 0;
@ -33,7 +44,8 @@ mp_zbar_pipeline_stop()
mp_pipeline_free(pipeline);
}
static bool is_3d_code(zbar_symbol_type_t type)
static bool
is_3d_code(zbar_symbol_type_t type)
{
switch (type) {
case ZBAR_EAN2:
@ -62,9 +74,41 @@ static bool is_3d_code(zbar_symbol_type_t type)
}
}
static MPZBarCode
process_symbol(const zbar_symbol_t *symbol)
static inline void
map_coords(int *x, int *y, int width, int height, int rotation, bool mirrored)
{
int x_r, y_r;
if (rotation == 0) {
x_r = *x;
y_r = *y;
} else if (rotation == 90) {
x_r = *y;
y_r = height - *x - 1;
} else if (rotation == 270) {
x_r = width - *y - 1;
y_r = *x;
} else {
x_r = width - *x - 1;
y_r = height - *y - 1;
}
if (mirrored) {
x_r = width - x_r - 1;
}
*x = x_r;
*y = y_r;
}
static MPZBarCode
process_symbol(const MPZBarImage *image, int width, int height, const zbar_symbol_t *symbol)
{
if (image->rotation == 90 || image->rotation == 270) {
int tmp = width;
width = height;
height = tmp;
}
MPZBarCode code;
unsigned loc_size = zbar_symbol_get_loc_size(symbol);
@ -100,6 +144,10 @@ process_symbol(const zbar_symbol_t *symbol)
code.bounds_y[3] = max_y;
}
for (uint8_t i = 0; i < 4; ++i) {
map_coords(&code.bounds_x[i], &code.bounds_y[i], width, height, image->rotation, image->mirrored);
}
const char *data = zbar_symbol_get_data(symbol);
unsigned int data_size = zbar_symbol_get_data_length(symbol);
code.type = zbar_get_symbol_name(type);
@ -110,25 +158,33 @@ process_symbol(const zbar_symbol_t *symbol)
}
static void
process_surface(MPPipeline *pipeline, cairo_surface_t **_surface)
process_image(MPPipeline *pipeline, MPZBarImage **_image)
{
cairo_surface_t *surface = *_surface;
MPZBarImage *image = *_image;
int width = cairo_image_surface_get_width(surface);
int height = cairo_image_surface_get_height(surface);
const uint32_t *surface_data = (const uint32_t *)cairo_image_surface_get_data(surface);
assert(image->pixel_format == MP_PIXEL_FMT_BGGR8
|| image->pixel_format == MP_PIXEL_FMT_GBRG8
|| image->pixel_format == MP_PIXEL_FMT_GRBG8
|| image->pixel_format == MP_PIXEL_FMT_RGGB8);
// Create a grayscale image for scanning from the current preview.
// Rotate/mirror correctly.
int width = image->width / 2;
int height = image->height / 2;
// Create a grayscale image for scanning from the current preview
uint8_t *data = malloc(width * height * sizeof(uint8_t));
for (size_t i = 0; i < width * height; ++i) {
data[i] = (surface_data[i] >> 16) & 0xff;
size_t i = 0;
for (int y = 0; y < image->height; y += 2) {
for (int x = 0; x < image->width; x += 2) {
data[++i] = image->data[x + image->width * y];
}
}
// Create image for zbar
zbar_image_t *zbar_image = zbar_image_create();
zbar_image_set_format(zbar_image, zbar_fourcc('Y', '8', '0', '0'));
zbar_image_set_size(zbar_image, width, height);
zbar_image_set_data(zbar_image, data, width * height * sizeof(uint8_t), zbar_image_free_data);
zbar_image_set_data(zbar_image, data, width * height * sizeof(uint8_t), NULL);
int res = zbar_scan_image(scanner, zbar_image);
assert(res >= 0);
@ -140,7 +196,7 @@ process_surface(MPPipeline *pipeline, cairo_surface_t **_surface)
const zbar_symbol_t *symbol = zbar_image_first_symbol(zbar_image);
for (int i = 0; i < MIN(res, 8); ++i) {
assert(symbol != NULL);
result->codes[i] = process_symbol(symbol);
result->codes[i] = process_symbol(image, width, height, symbol);
symbol = zbar_symbol_next(symbol);
}
@ -150,22 +206,57 @@ process_surface(MPPipeline *pipeline, cairo_surface_t **_surface)
}
zbar_image_destroy(zbar_image);
cairo_surface_destroy(surface);
mp_zbar_image_unref(image);
++frames_processed;
}
void
mp_zbar_pipeline_process_image(cairo_surface_t *surface)
mp_zbar_pipeline_process_image(MPZBarImage *image)
{
// If we haven't processed the previous frame yet, drop this one
if (frames_received != frames_processed) {
cairo_surface_destroy(surface);
mp_zbar_image_unref(image);
return;
}
++frames_received;
mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_surface, &surface,
sizeof(cairo_surface_t *));
mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &image,
sizeof(MPZBarImage *));
}
MPZBarImage *
mp_zbar_image_new(uint8_t *data,
MPPixelFormat pixel_format,
int width,
int height,
int rotation,
bool mirrored)
{
MPZBarImage *image = malloc(sizeof(MPZBarImage));
image->data = data;
image->pixel_format = pixel_format;
image->width = width;
image->height = height;
image->rotation = rotation;
image->mirrored = mirrored;
image->ref_count = 1;
return image;
}
MPZBarImage *
mp_zbar_image_ref(MPZBarImage *image)
{
++image->ref_count;
return image;
}
void
mp_zbar_image_unref(MPZBarImage *image)
{
if (--image->ref_count == 0) {
free(image->data);
free(image);
}
}

31
src/zbar_pipeline.h Normal file
View File

@ -0,0 +1,31 @@
#pragma once
#include "camera_config.h"
typedef struct _MPZBarImage MPZBarImage;
typedef struct {
int bounds_x[4];
int bounds_y[4];
char *data;
const char *type;
} MPZBarCode;
typedef struct {
MPZBarCode codes[8];
uint8_t size;
} MPZBarScanResult;
void mp_zbar_pipeline_start();
void mp_zbar_pipeline_stop();
void mp_zbar_pipeline_process_image(MPZBarImage *image);
MPZBarImage *mp_zbar_image_new(uint8_t *data,
MPPixelFormat pixel_format,
int width,
int height,
int rotation,
bool mirrored);
MPZBarImage *mp_zbar_image_ref(MPZBarImage *image);
void mp_zbar_image_unref(MPZBarImage *image);

View File

@ -1,120 +0,0 @@
#include "quickpreview.h"
#include <assert.h>
#include <glib.h>
#include <stdio.h>
static void
test_quick_preview_fuzz()
{
for (size_t i = 0; i < 10000; ++i) {
uint32_t width = rand() % 127 + 1;
uint32_t height = rand() % 127 + 1;
uint32_t format = rand() % (MP_PIXEL_FMT_MAX - 1) + 1;
assert(width > 0 && height > 0);
switch (format) {
case MP_PIXEL_FMT_BGGR8:
case MP_PIXEL_FMT_GBRG8:
case MP_PIXEL_FMT_GRBG8:
case MP_PIXEL_FMT_RGGB8:
width += width % 2;
height += height % 2;
break;
case MP_PIXEL_FMT_BGGR10P:
case MP_PIXEL_FMT_GBRG10P:
case MP_PIXEL_FMT_GRBG10P:
case MP_PIXEL_FMT_RGGB10P:
width += width % 2;
height += height % 2;
break;
case MP_PIXEL_FMT_UYVY:
case MP_PIXEL_FMT_YUYV:
width += width % 4;
break;
default:
assert(false);
}
int rotation;
switch (rand() % 3) {
case 0:
rotation = 0;
break;
case 1:
rotation = 90;
break;
case 2:
rotation = 180;
break;
default:
rotation = 270;
break;
}
bool mirrored = rand() % 2;
float matbuf[9];
float *colormatrix = NULL;
if (rand() % 2) {
for (int j = 0; j < 9; ++j) {
matbuf[j] = (double)rand() / RAND_MAX * 2.0;
}
colormatrix = matbuf;
}
uint8_t blacklevel = rand() % 3;
size_t src_size = mp_pixel_format_width_to_bytes(format, width) * height;
uint8_t *src = malloc(src_size);
for (int j = 0; j < src_size; ++j) {
src[j] = rand();
}
uint32_t preview_width = mp_pixel_format_width_to_colors(format, width);
uint32_t preview_height = mp_pixel_format_height_to_colors(format, height);
if (preview_width > 32 && preview_height > 32) {
preview_width /= (1 + rand() % 2);
preview_height /= (1 + rand() % 2);
}
uint32_t dst_width, dst_height, skip;
quick_preview_size(
&dst_width,
&dst_height,
&skip,
preview_width,
preview_height,
width,
height,
format,
rotation);
uint32_t *dst = malloc(dst_width * dst_height * sizeof(uint32_t));
quick_preview(
dst,
dst_width,
dst_height,
src,
width,
height,
format,
rotation,
mirrored,
colormatrix,
blacklevel,
skip);
free(dst);
free(src);
}
}
int
main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/quick_preview/fuzz", test_quick_preview_fuzz);
return g_test_run();
}

View File

@ -15,20 +15,6 @@ get_time()
return t.tv_sec + t.tv_usec * 1e-6;
}
void
on_capture(MPImage image, void *user_data)
{
size_t num_bytes =
mp_pixel_format_width_to_bytes(image.pixel_format, image.width) *
image.height;
uint8_t *data = malloc(num_bytes);
memcpy(data, image.data, num_bytes);
printf(" first byte: %d.", data[0]);
free(data);
}
int
main(int argc, char *argv[])
{
@ -184,7 +170,22 @@ main(int argc, char *argv[])
(last - start_capture) * 1000);
for (int i = 0; i < 10; ++i) {
mp_camera_capture_image(camera, on_capture, NULL);
MPBuffer buffer;
if (!mp_camera_capture_buffer(camera, &buffer)) {
printf(" Failed to capture buffer\n");
}
size_t num_bytes =
mp_pixel_format_width_to_bytes(m->pixel_format, m->width) *
m->height;
uint8_t *data = malloc(num_bytes);
memcpy(data, buffer.data, num_bytes);
printf(" first byte: %d.", data[0]);
free(data);
mp_camera_release_buffer(camera, buffer.index);
double now = get_time();
printf(" capture took %fms\n", (now - last) * 1000);

View File

@ -1,22 +0,0 @@
#pragma once
#include "camera_config.h"
typedef struct _cairo_surface cairo_surface_t;
typedef struct {
int bounds_x[4];
int bounds_y[4];
char *data;
const char *type;
} MPZBarCode;
typedef struct {
MPZBarCode codes[8];
uint8_t size;
} MPZBarScanResult;
void mp_zbar_pipeline_start();
void mp_zbar_pipeline_stop();
void mp_zbar_pipeline_process_image(cairo_surface_t *surface);