Currently, I'm writing a function for our company's micro-library in Kotlin that reads bytes of an image and returns orientation in degrees
I know, that in API 24 we have ExifInterface and ability to instantiate it from InputStream, but the problem is that we need to support API 21, which doesn't have such constructor.
Byte array that is passed to getOrientation function always looks like this:
-1, -40, -1, -32, 0, 16, 74, 70, 73, 70, 0, 1, 1, 0, 0, 72, 0, 72, 0, 0, -1, -31, 8, 82, 69, 120, 105, 102, 0, 0, 77, 77, 0, 42, 0, 0, 0, 8, 0, 12, 1, 15, 0, 2, 0, 0, 0, 6, 0, 0, 0, -98, 1, 16, 0, 2, 0, 0, 0, 9, 0, 0, 0, -92, 1, 18, 0, 3, 0, 0, 0, 1, 0, 6, 0, 0, 1, 26, 0, 5, 0, 0, 0, 1, 0, 0, 0, -82, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0, -74, 1, 40, 0 and so on
It looks like shifted, and this is the reason why I shift it right by 256 in the first line
Here's the code I'm stuck on for now:
object Exif {
fun getOrientation(_bytes: ByteArray): Int {
val bytes = _bytes.map { b -> b.toInt() + 256 }
if (bytes[0] != 0xff && bytes[1] != 0xd8) {
return 0
}
val length = bytes.size
var offset = 2
while (offset < length) {
// TODO: extract all operations like the following
// into separate function
val marker = (bytes[offset] shl 8) or bytes[offset + 1]
offset += 2
if (marker == 0xffe1) {
offset += 2
val exifStr = (bytes[offset] shl 24) or (bytes[offset + 1] shl 16) or (bytes[offset + 2] shl 8) or bytes[offset + 3]
if (exifStr != 0x45786966) {
return 0
}
offset += 6
val little = (bytes[offset] shl 8) or bytes[offset + 1] == 0x4949
offset += 4
val inc = (bytes[offset] shl 24) or (bytes[offset + 1] shl 16) or (bytes[offset + 2] shl 8) or bytes[offset + 3]
offset += if (little) inc.reverseBytes() else inc
val tagsWOEndian = (bytes[offset] shl 8) or bytes[offset + 1]
val tags = if (little) tagsWOEndian.reverseBytes() else tagsWOEndian
offset += 2
for (idx in 0..tags) {
val off = offset + idx * 12
val orientWOEndian = (bytes[off] shl 8) or bytes[off + 1]
val orient = if (little) orientWOEndian.reverseBytes() else orientWOEndian
if (orient == 0x0112) {
when ((bytes[off + 8] shl 8) or bytes[off + 8 + 1]) {
1 -> return 0
3 -> return 180
6 -> return 90
8 -> return 270
}
}
}
} else if (marker and 0xff00 != 0xff00) {
break
} else {
offset += (bytes[offset] shl 8) or bytes[offset + 1]
}
}
return 0
}
}
fun Int.reverseBytes(): Int {
val v0 = ((this ushr 0) and 0xFF)
val v1 = ((this ushr 8) and 0xFF)
val v2 = ((this ushr 16) and 0xFF)
val v3 = ((this ushr 24) and 0xFF)
return (v0 shl 24) or (v1 shl 16) or (v2 shl 8) or (v3 shl 0)
}
Solved!
I ended up with the following solution:
fun getUint16(bytes: List<Int>, offset: Int, littleEndian: Boolean): Int {
val value = (bytes[offset] shl 8) or bytes[offset + 1]
return if (littleEndian) value.reverseBytes() else value
}
fun getUint32(bytes: List<Int>, offset: Int, littleEndian: Boolean): Int {
val value = (bytes[offset] shl 24) or (bytes[offset + 1] shl 16) or (bytes[offset + 2] shl 8) or bytes[offset + 3]
return if (littleEndian) value.reverseBytes() else value
}
fun Int.reverseBytes(): Int {
val v0 = ((this ushr 0) and 0xFF)
val v1 = ((this ushr 8) and 0xFF)
val v2 = ((this ushr 16) and 0xFF)
val v3 = ((this ushr 24) and 0xFF)
return (v0 shl 24) or (v1 shl 16) or (v2 shl 8) or (v3 shl 0)
}
object Exif {
fun getRotationDegrees(_bytes: ByteArray): Int {
val bytes = _bytes.take(64 * 1024).map { b -> b.toInt() and 0xff }
if (getUint16(bytes, 0, false) != 0xffd8) {
return 0
}
val length = bytes.size
var offset = 2
while (offset < length) {
val marker = getUint16(bytes, offset, false)
offset += 2
if (marker == 0xffe1) {
if (getUint32(bytes, offset + 2, false) != 0x45786966) {
return 0
}
offset += 2
val little = getUint16(bytes, offset + 6, false) == 0x4949
offset += 6
offset += getUint32(bytes, offset + 4, little)
val tags = getUint16(bytes, offset, little)
offset += 2
for (idx in 0..tags) {
if (getUint16(bytes, offset + (idx * 12), little) == 0x0112) {
when (getUint16(bytes, offset + (idx * 12) + 8, little)) {
1 -> return 0
3 -> return 180
6 -> return 90
8 -> return 270
}
}
}
} else if (marker and 0xff00 != 0xff00) {
break
} else {
offset += getUint16(bytes, offset, false)
}
}
return 0
}
}
Changes in comparison to code posted in the question:
The transformation between the signed _bytes and unsigned bytes was performed wrong. Now I'm getting the first 65536 bytes first, then transforming them to unsigned by applying bitwise and to each byte instead of simply adding 256
All operations for getting UInt16 and UInt32 values from the byte array were moved to separate functions
Fixed the wrong offset incrementation inside while loop
Related
I am developing a drawing app for android that allows the user to export its canvas to png. I looked on internet on how to do this and it seems that the solution is to use the export_to_png method. On Debian GNU/Linux 11, it works but when I used buildozer to make an android app it doesn't work. My real issue here is that even though I give enough permissions to the app, it simply does not create the png file. Also, I am using PIL to crop the image so the drawing does not include the buttons of the application.
Could anyone tell me how to export a canvas to png in android using kivy?
Could anyone tell me how to crop an image using kivy so I don't have to use PIL?
Could anyone tell me how to export only the canvas so I don't have to crop the image?
By the way, in the export method I used multiple try blocks in order to figure out why my application was crashing each time I tried to export to png. By doing this, I realized that the self.export_to_png("my_plan.png") command was not working. In other words, it does not create the file. I don't think that granting permissions to the app is the problem since the save method creates the data.txt file to save the progress of the user. Also, the load method also works by reading the previously created file. This, however, is not the case for the export method.
Thank you.
This is my main.py code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.widget import Widget
from kivy.graphics import Line
from kivy.graphics import Rectangle
from kivy.graphics import Color
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from PIL import Image
from os import path
class MyRelativeLayout(RelativeLayout):
def __init__(self, **kwargs):
super(MyRelativeLayout, self).__init__(**kwargs)
self.active_tool = "Line"
self.temp_point = [0, 0]
self.dx = 0
self.dy = 0
self.move_factor = 10
self.elements = [Line(points=(0,0), width = 0)]
self.redo_list = []
self.eraser_size = 100
self.eraser = Rectangle(pos = [0, 0], size = [0, 0])
self.more_buttons = RelativeLayout()
self.back_button = Button(text = "Back", pos_hint = {"x": 0, "top": 1}, size_hint = (0.10, 0.10), background_color = (0, 0 ,1, 1))
self.back_button.bind(on_press = self.back)
self.undo_button = Button(text = "Undo", pos_hint = {"x": 0.10, "top": 1}, size_hint = (0.10, 0.10), background_color = (0, 0 ,1, 1))
self.undo_button.bind(on_press = self.undo)
self.redo_button = Button(text = "Redo", pos_hint = {"x": 0.20, "top": 1}, size_hint = (0.10, 0.10), background_color = (0, 0 ,1, 1))
self.redo_button.bind(on_press = self.redo)
self.export_button = Button(text = "Export", pos_hint = {"x": 0.30, "top": 1}, size_hint = (0.10, 0.10), background_color = (0, 0 ,1, 1))
self.export_button.bind(on_press = self.export)
self.more_buttons.add_widget(self.back_button)
self.more_buttons.add_widget(self.undo_button)
self.more_buttons.add_widget(self.redo_button)
self.more_buttons.add_widget(self.export_button)
self.more = Popup(title = "More Buttons", content = self.more_buttons, size_hint = [1, 1])
self.button_size = 100
self.im = None
def on_touch_down(self, touch):
if not(touch.x < self.width * 0.10):
if self.active_tool == "Line":
with self.canvas:
Color(0, 0, 0, 1, mode="rgba")
self.elements.append(Line(points = [touch.x, touch.y], width = 2))
elif self.active_tool == "Rectangle":
with self.canvas:
Color(0, 0, 0, 1, mode="rgba")
self.elements.append(Line(points = [touch.x, touch.y], width = 2))
elif self.active_tool == "Free Hand Drawing":
with self.canvas:
Color(0, 0, 0, 1, mode="rgba")
self.elements.append(Line(points = [touch.x, touch.y], width = 2))
elif self.active_tool == "Eraser":
with self.canvas:
Color(1, 1, 1, 1, mode = "rgba")
self.elements.append(Rectangle(pos = [touch.x - self.eraser_size / 2, touch.y - self.eraser_size / 2], size = [self.eraser_size, self.eraser_size]))
elif self.active_tool == "Move":
self.temp_point = [touch.x, touch.y]
else:
print(self.active_tool + " was not found")
return super(MyRelativeLayout, self).on_touch_down(touch)
def on_touch_move(self, touch):
if not(touch.x < self.width * 0.10):
if self.active_tool == "Line":
self.elements[-1].points = [self.elements[-1].points[0], self.elements[-1].points[1], touch.x, touch.y]
elif self.active_tool == "Rectangle":
self.elements[-1].points = [self.elements[-1].points[0], self.elements[-1].points[1], touch.x, self.elements[-1].points[1], touch.x, touch.y, self.elements[-1].points[0], touch.y, self.elements[-1].points[0], self.elements[-1].points[1]]
elif self.active_tool == "Free Hand Drawing":
self.elements[-1].points += [touch.x, touch.y]
elif self.active_tool == "Eraser":
with self.canvas:
Color(1, 1, 1, 1, mode = "rgba")
self.elements.append(Rectangle(pos = [touch.x - self.eraser_size / 2, touch.y - self.eraser_size / 2], size = [self.eraser_size, self.eraser_size]))
Color(0, 0, 0, 1, mode = "rgba")
elif self.active_tool == "Move":
self.dx = touch.x - self.temp_point[0]
self.dy = touch.y - self.temp_point[1]
with self.canvas:
for i in range(0, len(self.elements)):
if self.get_element(self.elements[i]) == "<class 'kivy.graphics.vertex_instructions.Line'>":
self.canvas.remove(self.elements[i])
self.elements[i] = self.move_line(self.elements[i], self.dx, self.dy)
elif self.get_element(self.elements[i] == "<class 'kivy.graphics.vertex_instructions.Rectangle'>"):
self.canvas.remove(self.elements[i])
Color(1, 1, 1, 1, mode = "rgba")
self.elements[i] = self.move_eraser(self.elements[i], self.dx, self.dy)
Color(0, 0, 0, 1, mode = "rgba")
else:
print(self.active_tool + " was not found")
return super(MyRelativeLayout, self).on_touch_move(touch) # Using super to have the button behavior so we can press the buttons
def on_touch_up(self, touch):
if self.active_tool == "Line":
pass
elif self.active_tool == "Rectangle":
pass
elif self.active_tool == "Free Hand Drawing":
pass
elif self.active_tool == "Eraser":
# with self.canvas:
# self.canvas.remove(self.eraser)
pass
elif self.active_tool == "Move":
self.dx = 0
self.dy = 0
self.temp_point = [0, 0]
else:
print(self.active_tool + " was not found")
def back(self, value):
self.more.dismiss()
def undo(self, value):
# method to undo drawings
if len(self.elements) > 0: # verifying that elements list is not empty
self.canvas.remove(self.elements[-1]) # removing the last drawn element from canvas
self.redo_list.append(self.elements[-1]) # adding the last drawn element to the redo list
self.elements.pop() # deleting the last element in elements list
self.more.dismiss() # closing the popup window
def redo(self, value):
# method to redo drawings
if len(self.redo_list) > 0: # verifying that the redo list is not empty
self.canvas.add(Color(0, 0, 0, 1)) # Changing color to black
self.elements.append(self.redo_list[-1]) # adding the last element in the redo list to the element list
self.canvas.add(self.redo_list[-1]) # adding the last element in the redo list to canvas
self.redo_list.pop() # deleting the last element in redo list
self.more.dismiss()
def export(self, value):
try:
self.export_to_png("my_plan.png")
template = str(path.isfile("my_plan.png"))
popup = Popup(title = "Error", content = Label(text = template))
popup.open()
except Exception as ex:
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
template = str(template.format(type(ex).__name__, ex.args))
popup = Popup(title = "Error", content = Label(text = template))
popup.open()
try:
self.im = Image.open("my_plan.png")
except Exception as ex:
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
template = str(template.format(type(ex).__name__, ex.args))
popup = Popup(title = "Error", content = Label(text = template))
popup.open()
try:
self.im = self.im.crop((int(self.width * 0.10), int(0), int(self.width), int(self.height)))
except Exception as ex:
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
template = str(template.format(type(ex).__name__, ex.args))
popup = Popup(title = "Error", content = Label(text = template))
popup.open()
try:
self.im.save("/storage/emulated/0/my_plan.png")
except Exception as ex:
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
template = str(template.format(type(ex).__name__, ex.args))
popup = Popup(title = "Error", content = Label(text = template))
popup.open()
self.more.dismiss()
def zoom_in(self):
scale_factor = 1.25
with self.canvas:
for i in range(0, len(self.elements)):
if self.get_element(self.elements[i]) == "<class 'kivy.graphics.vertex_instructions.Line'>":
self.canvas.remove(self.elements[i])
self.elements[i] = self.scale_line(self.elements[i], scale_factor)
elif self.get_element(self.elements[i] == "<class 'kivy.graphics.vertex_instructions.Rectangle'>"):
self.canvas.remove(self.elements[i])
Color(1, 1, 1, 1, mode = "rgba")
self.elements[i] = self.scale_eraser(self.elements[i], scale_factor)
Color(0, 0, 0, 1, mode = "rgba")
self.move_factor += 3
self.eraser_size *= 1.25
def zoom_out(self):
scale_factor = 0.75
with self.canvas:
for i in range(0, len(self.elements)):
if self.get_element(self.elements[i]) == "<class 'kivy.graphics.vertex_instructions.Line'>":
self.canvas.remove(self.elements[i])
self.elements[i] = self.scale_line(self.elements[i], scale_factor)
elif self.get_element(self.elements[i] == "<class 'kivy.graphics.vertex_instructions.Rectangle'>"):
self.canvas.remove(self.elements[i])
Color(1, 1, 1, 1, mode = "rgba")
self.elements[i] = self.scale_eraser(self.elements[i], scale_factor)
Color(0, 0, 0, 1, mode = "rgba")
self.move_factor -= 3
self.eraser_size *= 0.75
def move_line(self, line, dx, dy):
new_coordinates = []
for i in range(0, len(line.points)):
if i % 2 == 0:
if dx >= 0 and dy >= 0:
new_coordinates.append(line.points[i] + self.move_factor)
new_coordinates.append(line.points[i+1] + self.move_factor)
elif dx <= 0 and dy >= 0:
new_coordinates.append(line.points[i] - self.move_factor)
new_coordinates.append(line.points[i+1] + self.move_factor)
elif dx >= 0 and dy <= 0:
new_coordinates.append(line.points[i] + self.move_factor)
new_coordinates.append(line.points[i+1] - self.move_factor)
elif dx <= 0 and dy <= 0:
new_coordinates.append(line.points[i] - self.move_factor)
new_coordinates.append(line.points[i+1] - self.move_factor)
else:
print("Unknown direction.")
else:
continue
return Line(points = new_coordinates.copy(), width = 2)
def scale_line(self, line, scale):
new_coordinates = []
for i in range(0, len(line.points)):
new_coordinates.append(line.points[i] * scale)
return Line(points = new_coordinates.copy(), width = 2)
def move_eraser(self, eraser, dx, dy):
if dx >= 0 and dy >= 0:
return Rectangle(pos = [eraser.pos[0] + self.move_factor, eraser.pos[1] + self.move_factor], size = eraser.size)
elif dx <= 0 and dy >= 0:
return Rectangle(pos = [eraser.pos[0] - self.move_factor, eraser.pos[1] + self.move_factor], size = eraser.size)
elif dx >= 0 and dy <= 0:
return Rectangle(pos = [eraser.pos[0] + self.move_factor, eraser.pos[1] - self.move_factor], size = eraser.size)
elif dx <= 0 and dy <= 0:
return Rectangle(pos = [eraser.pos[0] - self.move_factor, eraser.pos[1] - self.move_factor], size = eraser.size)
def scale_eraser(self, eraser, scale):
return Rectangle(pos = [eraser.pos[0] * scale, eraser.pos[1] * scale], size = [eraser.size[0] * scale, eraser.size[1] * scale])
def get_element(self, element):
return str(type(element))
def save_element(self, element):
line = ""
if self.get_element(element) == "<class 'kivy.graphics.vertex_instructions.Line'>":
line += "Line"
for coordinate in element.points:
line += "," + str(coordinate)
elif self.get_element(element) == "<class 'kivy.graphics.vertex_instructions.Rectangle'>":
line += "Rectangle," + str(element.pos[0]) + "," + str(element.pos[1]) + "," + str(element.size[0]) + "," + str(element.size[1])
return line
def save(self):
file = open("data.txt", "w")
data = ""
for element in self.elements:
data += self.save_element(element) + "\n"
file.write(data)
file.close()
def load(self):
for i in range(0, len(self.elements)):
with self.canvas:
self.canvas.remove(self.elements[i])
self.elements = []
try:
file = open("data.txt", "r")
except Exception as ex:
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
print(template.format(type(ex).__name__, ex.args))
else:
lines = file.readlines()
tokens = ""
temp_points = []
for line in lines:
tokens = line.split(",")
if tokens[0] == "Line":
for i in range(1, len(tokens)):
temp_points.append(float(tokens[i]))
with self.canvas:
Color(0, 0, 0, 1, mode="rgba")
self.elements.append(Line(points = temp_points, width = 2))
elif tokens[0] == "Rectangle":
with self.canvas:
Color(1, 1, 1, 1, mode="rgba")
self.elements.append(Rectangle(pos = [float(tokens[1]), float(tokens[2])], size = [float(tokens[3]), float(tokens[4])]))
Color(0, 0, 0, 1, mode="rgba")
temp_points = []
file.close()
def change_active_tool_line(self):
self.active_tool = "Line"
def change_active_tool_rectangle(self):
self.active_tool = "Rectangle"
def change_active_tool_free_hand_drawing(self):
self.active_tool = "Free Hand Drawing"
def change_active_tool_eraser(self):
self.active_tool = "Eraser"
def change_active_tool_move(self):
self.active_tool = "Move"
class MyPlanApp(App):
def build(self):
return MyRelativeLayout()
MyPlanApp().run()
This is my myplan.kv file code
<MyRelativeLayout>:
RelativeLayout:
id: drawing_area
size_hint: 0.90, 1
pos_hint: {"x": 0.10, "y": 0}
canvas:
Color:
rgb: 1, 1, 1
Rectangle:
group: "white_background"
size: self.size
Color:
rgb: 1, 0, 0
RelativeLayout:
size_hint: 0.10, 1
pos_hint: {"x": 0, "top": 1}
Button:
text: "Hand"
pos_hint: {"x": 0, "top": 1}
size_hint: 1, 0.10
background_color: 0, 0, 1, 1
on_press: root.change_active_tool_free_hand_drawing()
Button:
text: "Line"
pos_hint: {"x": 0, "top": 0.90}
size_hint: 1, 0.10
background_color: 0, 0, 1, 1
on_press: root.change_active_tool_line()
Button:
text: "Rectangle"
pos_hint: {"x": 0, "top": 0.80}
size_hint: 1, 0.10
background_color: 0, 0, 1, 1
on_press: root.change_active_tool_rectangle()
Button:
text: "Eraser"
pos_hint: {"x": 0, "top": 0.70}
size_hint: 1, 0.10
background_color: 0, 0, 1, 1
on_press: root.change_active_tool_eraser()
Button:
text: "Zoom in"
pos_hint: {"x": 0, "top": 0.60}
size_hint: 1, 0.10
background_color: 0, 0, 1, 1
on_press: root.zoom_in()
Button:
text: "Zoom out"
pos_hint: {"x": 0, "top": 0.50}
size_hint: 1, 0.10
background_color: 0, 0, 1, 1
on_press: root.zoom_out()
Button:
text: "Move"
pos_hint: {"x": 0, "top": 0.40}
size_hint: 1, 0.10
background_color: 0, 0, 1, 1
on_press: root.change_active_tool_move()
Button:
text: "Save"
pos_hint: {"x": 0, "top": 0.30}
size_hint: 1, 0.10
background_color: 0, 0, 1, 1
on_press: root.save()
Button:
text: "Load"
pos_hint: {"x": 0, "top": 0.20}
size_hint: 1, 0.10
background_color: 0, 0, 1, 1
on_press: root.load()
Button:
text: "More"
pos_hint: {"x": 0, "top": 0.10}
size_hint: 1, 0.10
background_color: 0, 0, 1, 1
on_press: root.more.open()
I also included PIL in my buildozer.spec file.
I am trying to make the corners of horizontal bar chart rounded, here's the CustomChartRenderer code:
package com.almaraiquest.Utils
import android.graphics.*
import com.github.mikephil.charting.animation.ChartAnimator
import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet
import com.github.mikephil.charting.renderer.HorizontalBarChartRenderer
import com.github.mikephil.charting.utils.ViewPortHandler
class MyCustomChartRender(chart: BarDataProvider, animator: ChartAnimator, viewPortHandler: ViewPortHandler) : HorizontalBarChartRenderer(chart, animator, viewPortHandler) {
override fun drawDataSet(c: Canvas?, dataSet: IBarDataSet?, index: Int) {
super.drawDataSet(c, dataSet, index)
val trans = mChart.getTransformer(dataSet!!.axisDependency)
mShadowPaint.color = dataSet.barShadowColor
val phaseX = mAnimator.phaseX
val phaseY = mAnimator.phaseY
// initialize the buffer
// initialize the buffer
val buffer = mBarBuffers[index]
buffer.setPhases(phaseX, phaseY)
buffer.setDataSet(index)
buffer.setInverted(mChart.isInverted(dataSet.axisDependency))
buffer.feed(dataSet)
trans.pointValuesToPixel(buffer.buffer)
val length = buffer.buffer.size
var left = 0f
var right = 0f
val top = buffer.buffer[length - 3]
val bot = buffer.buffer[length - 1]
var leftSaved = false
var j = 0
while (j < buffer.size()) {
if (!mViewPortHandler.isInBoundsTop(buffer.buffer[j + 3])) break
if (!mViewPortHandler.isInBoundsBottom(buffer.buffer[j + 1])) {
j += 4
continue
}
// Set the color for the currently drawn value.
// If the index is
// out of bounds, reuse colors.
val color = dataSet.getColor(j / 4)
mRenderPaint.color = color
if (color != 0 && !leftSaved) {
leftSaved = true
left = buffer.buffer[j]
}
if (j > 4) { // it works but its ugly
right = buffer.buffer[j - 2]
}
c!!.drawRect(buffer.buffer[j], buffer.buffer[j + 1] + 10, buffer.buffer[j + 2],
buffer.buffer[j + 3] - 10, mRenderPaint)
j += 4
}
val erasePaint = Paint()
erasePaint.setAntiAlias(true)
erasePaint.setStyle(Paint.Style.STROKE)
val paintWidth = 20f
erasePaint.setStrokeWidth(paintWidth)
erasePaint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.CLEAR))
c!!.drawRoundRect(RectF(left - paintWidth / 2, top, right + paintWidth / 2, bot), 30f, 30f, erasePaint)
}
}
But its resulting in like:
But i want it like this way:
So what i am doing wrong here? I have looked for other threads too but none of them claims for horizontal bar chart for both negative and positive bars so please help me to solve this issue.
Update:
Here's the settings for horizontal bar chart:
private fun setHorizontalChart(data : BarData, brand: ArrayList<String>){
horizonatal_chart.setDrawBarShadow(false)
val description = Description()
description.text = ""
horizonatal_chart.description = description
horizonatal_chart.legend.setEnabled(false)
horizonatal_chart.setPinchZoom(false)
horizonatal_chart.setDrawValueAboveBar(false)
horizonatal_chart.setScaleEnabled(false)
horizonatal_chart.setDrawValueAboveBar(true)
//Display the axis on the left (contains the labels 1*, 2* and so on)
val xAxis = horizonatal_chart.getXAxis()
xAxis.setDrawGridLines(false)
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM)
xAxis.setEnabled(true)
xAxis.setDrawAxisLine(false)
xAxis.textColor = Color.parseColor("#a1a1a1")
val yLeft = horizonatal_chart.axisLeft
//Set the minimum and maximum bar lengths as per the values that they represent
yLeft.axisMaximum = 100f
yLeft.axisMinimum = 0f
yLeft.isEnabled = false
//Now add the labels to be added on the vertical axis
xAxis.valueFormatter = IAxisValueFormatter { value, axis -> brand[value.toInt()] }
val yRight = horizonatal_chart.axisRight
yRight.setDrawAxisLine(true)
yRight.setDrawGridLines(false)
yRight.isEnabled = false
//Set bar entries and add necessary formatting
horizonatal_chart.axisLeft.setAxisMinimum(data.yMin)
data.barWidth = 0.9f
val myCustomChartRender = MyCustomChartRender(horizonatal_chart, horizonatal_chart.animator, horizonatal_chart.viewPortHandler)
//Add animation to the graph
horizonatal_chart.renderer = myCustomChartRender
horizonatal_chart.animateY(2000)
horizonatal_chart.data = data
horizonatal_chart.setTouchEnabled(false)
horizonatal_chart.invalidate()
}
It is little late to answer here but may be helpful for others. We
can achieve it by little custom coding.
first of all we need to create a custom class
public class RoundedHorizontalBarChartRenderer extends HorizontalBarChartRenderer {
public RoundedHorizontalBarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
private float mRadius=5f;
public void setmRadius(float mRadius) {
this.mRadius = mRadius;
}
#Override
protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
mShadowPaint.setColor(dataSet.getBarShadowColor());
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
// initialize the buffer
BarBuffer buffer = mBarBuffers[index];
buffer.setPhases(phaseX, phaseY);
buffer.setDataSet(index);
buffer.setBarWidth(mChart.getBarData().getBarWidth());
buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency()));
buffer.feed(dataSet);
trans.pointValuesToPixel(buffer.buffer);
// if multiple colors
if (dataSet.getColors().size() > 1) {
for (int j = 0; j < buffer.size(); j += 4) {
if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2]))
continue;
if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j]))
break;
if (mChart.isDrawBarShadowEnabled()) {
if (mRadius > 0)
c.drawRoundRect(new RectF(buffer.buffer[j], mViewPortHandler.contentTop(),
buffer.buffer[j + 2],
mViewPortHandler.contentBottom()), mRadius, mRadius, mShadowPaint);
else
c.drawRect(buffer.buffer[j], mViewPortHandler.contentTop(),
buffer.buffer[j + 2],
mViewPortHandler.contentBottom(), mShadowPaint);
}
// Set the color for the currently drawn value. If the index
// is
// out of bounds, reuse colors.
mRenderPaint.setColor(dataSet.getColor(j / 4));
if (mRadius > 0)
c.drawRoundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
buffer.buffer[j + 3]), mRadius, mRadius, mRenderPaint);
else
c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
buffer.buffer[j + 3], mRenderPaint);
}
} else {
mRenderPaint.setColor(dataSet.getColor());
for (int j = 0; j < buffer.size(); j += 4) {
if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2]))
continue;
if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j]))
break;
if (mChart.isDrawBarShadowEnabled()) {
if (mRadius > 0)
c.drawRoundRect(new RectF(buffer.buffer[j], mViewPortHandler.contentTop(),
buffer.buffer[j + 2],
mViewPortHandler.contentBottom()), mRadius, mRadius, mShadowPaint);
else
c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
buffer.buffer[j + 3], mRenderPaint);
}
if (mRadius > 0)
c.drawRoundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
buffer.buffer[j + 3]), mRadius, mRadius, mRenderPaint);
else
c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
buffer.buffer[j + 3], mRenderPaint);
}
}
}
}
now, in your Activity
BarChart barchart = (BarChart) findViewById( R.id.barchart );
RoundedHorizontalBarChartRenderer roundedBarChartRenderer= new RoundedHorizontalBarChartRenderer(barchart , barchart.getAnimator(), barchart.getViewPortHandler());
roundedBarChartRenderer.setmRadius(20f);
barchart.setRenderer(roundedBarChartRenderer);
Hope this is helpful, please accept the answer if found helpful.
I am relatively new to d3js, and trying to create a visualization of my quartet's concert schedule using a map. My first attempt works great in a desktop chrome browser and desktop safari browser.
On my android device in mobile chrome, the map renders the entire globe incorrectly except for the United States.
http://test.chiaraquartet.net/topo/index.html
Any insight into what I am doing wrong/if there is a bug in d3 would be appreciated.
Here is the code in question:
var center = [90, -38.7],
ratio = window.devicePixelRatio || 1,
graticule = d3.geo.graticule(),
width = 500,
height = 500,
degrees = 180 / Math.PI,
projection = d3.geo.orthographic()
.scale(height / 2 - 1)
.rotate(center)
.translate([width / 2, height / 2])
.clipAngle(90)
.precision(.1)
var graticule = d3.geo.graticule()()
// Round to integer pixels for speed, and set pixel ratio.
function roundRatioContext(context) {
return {
moveTo: function(x, y) { context.moveTo(Math.round(x * ratio), Math.round(y * ratio)); },
lineTo: function(x, y) { context.lineTo(Math.round(x * ratio), Math.round(y * ratio)); },
closePath: function() { context.closePath(); }
};
}
var canvas = d3.select("body").append("canvas")
.attr("width", width * ratio)
.attr("height", height * ratio)
.style("width", width + "px")
.style("height", height + "px")
var c = canvas.node().getContext("2d");
var path = d3.geo.path()
.projection(projection)
.context(roundRatioContext(c));
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append('g')
var textbox = d3.select("body").append("div")
.style('position', 'absolute')
.attr('class', 'popup')
.classed('hidden', true)
var sphere = svg.append("path")
.datum({type: "Sphere"})
.attr("id", "sphere")
.attr("d", path)
var runner = queue()
.defer(d3.json, 'world.json')
.defer(d3.xhr('/concertrpc.php')
.header('content-type', 'application/json')
.post, JSON.stringify({
params: [],
id: 1
}))
.await(function(error, world, info) {
if (error) return console.error(error);
var features = topojson.feature(world, world.objects.states)
var countries = topojson.feature(world, world.objects.countries)
console.log(world)
var concerts = JSON.parse(info.responseText)
if (concerts.error) {
console.log(concerts.error)
return
}
concerts = concerts.result.concerts
console.log(concerts)
var globe = {type: "Sphere"},
graticule = d3.geo.graticule()(),
countries = topojson.feature(world, world.objects.countries),
states = topojson.feature(world, world.objects.states),
country_borders = topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b }),
state_borders = topojson.mesh(world, world.objects.states, function(a, b) { return a.id !== b.id }),
temp_context
temp_context = path.context()
path.context(null)
svg.selectAll('.concert')
.data(concerts, function(d) { return d.properties.id })
.enter().append("path")
.attr('class', 'concert')
.attr('d', path)
.on('mouseover', concertMouseover)
.on('mouseout', function(d) {
textbox.classed('hidden', true)
})
path.context(temp_context)
d3.select('body').append('div').append('button')
.attr('type', 'button')
.text('This season')
.on('click', function() {
getConcertData('This season')
})
var zoom = d3.geo.zoom()
.projection(projection)
.duration(function(S) { return 2000 * Math.sqrt(S); }) // assume ease="quad-in-out"
.scaleExtent([height / 2 - 1, Infinity])
.on("zoom", function() {
projection.clipAngle(Math.asin(Math.min(1, .5 * Math.sqrt(width * width + height * height) / projection.scale())) * degrees);
c.clearRect(0, 0, width * ratio, height * ratio);
c.strokeStyle = "#999", c.lineWidth = .25 * ratio, c.beginPath(), path(graticule), c.stroke();
c.fillStyle = "#69d2e7", c.beginPath(), path(countries), c.fill();
c.fillStyle = "#00f", c.beginPath(), path(states), c.fill();
c.strokeStyle = "#fff", c.lineWidth = .5 * ratio, c.beginPath(), path(country_borders), c.stroke();
c.strokeStyle = "#fff", c.lineWidth = .5 * ratio, c.beginPath(), path(state_borders), c.stroke();
c.strokeStyle = "#000", c.lineWidth = .5 * ratio, c.beginPath(), path(globe), c.stroke();
temp_context = path.context()
path.context(null)
svg.selectAll("path").attr("d",path);
path.context(temp_context)
})
//.on("zoomend", transition);
canvas
.call(zoom)
.call(zoom.event);
sphere
.call(zoom)
function transition() {
zoomBounds(projection, states.features[30]);
canvas.transition()
.ease("quad-in-out")
.duration(2000) // see https://github.com/mbostock/d3/pull/2045
.call(zoom.projection(projection).event);
}
function zoomBounds(projection, o) {
var centroid = d3.geo.centroid(o),
clip = projection.clipExtent();
projection
.rotate([-centroid[0], -centroid[1]])
.clipExtent(null)
.scale(1)
.translate([0, 0]);
var b = path.bounds(o),
k = Math.min(1000, .45 / Math.max(Math.max(Math.abs(b[1][0]), Math.abs(b[0][0])) / width, Math.max(Math.abs(b[1][1]), Math.abs(b[0][1])) / height));
projection
.clipExtent(clip)
.scale(k)
.translate([width / 2, height / 2]);
}
})
function concertMouseover(d) {
var loc = projection(d3.select(this).datum().geometry.coordinates)
textbox.style('top', loc[1] + "px")
textbox.style('left', loc[0] + 15 + "px")
textbox.text(d.properties.title)
textbox.classed('hidden', false)
}
function getConcertData(request) {
d3.xhr('/concertrpc.php')
.header('content-type', 'application/json')
.post(JSON.stringify({
params: [request],
id: 1
}), function(error, info) {
var concerts = JSON.parse(info.responseText)
if (concerts.error) {
console.log(concerts.error)
return
}
var c = svg.selectAll('.concert')
.data(concerts.result.concerts, function(d) { return d.properties.id })
c.transition()
.style('opacity', '1')
c.enter()
.append('path')
.attr('class', 'concert')
.attr('d', path)
.on('mouseover', concertMouseover)
.on('mouseout', function(d) {
textbox.classed('hidden', true)
})
c.exit().transition()
.duration(1000)
.style('opacity', '0')
.remove()
})
}
I'm not sure what was causing the problem, but the culprit was in the topojson file generated by my attempts to combine a map of the countries of the world with a map of the states of the USA. I found a different map of the world to use, and now the map displays the same way on both the phone and the desktop.
trying to use slide menu from function https://coronalabs.com/blog/2014/04/08/widgets-creating-a-sliding-panel/
I followed exactly the suggested code and I created a panel with 3 texts and one button. Then I added an event to the button to close the panel, but it does not work. Cannot understand why..:(. Here is the code:
local panel = widget.newPanel
{
location = "left",
--onComplete = panelTransDone,
width = display.contentWidth * 0.8,
height = display.contentHeight * 0.8,
speed = 500,
--inEasing = easing.outBack,
outEasing = easing.outCubic
}
panel.background = display.newRect( 100, -50, 900, 730 )
panel.background:setFillColor( 0.1, 0.1, 0.1, 0.9 )
panel:insert( panel.background )
panel.item1 = display.newText( "GAME DESCRIPTION", 0, 0, native.systemFontBold, 40 )
panel.item1:setFillColor( 1, 1, 0 )
panel.item1.x = -95
panel.item1.y = -350
panel:insert( panel.item1 )
panel.item2 = display.newText( "SET SOUNDS", 0, 0, native.systemFontBold, 40 )
panel.item2:setFillColor( 1, 1, 0 )
panel.item2.x = -170
panel.item2.y = -250
panel:insert( panel.item2 )
panel.item3 = display.newText( "CHECK SCORE", 0, 0, native.systemFontBold, 40 )
panel.item3:setFillColor( 1, 1, 0 )
panel.item3.x = -150
panel.item3.y = -150
panel:insert( panel.item3 )
panel.item4 = display.newText( "CLOSE", 0, 0, native.systemFontBold, 40 )
panel.item4:setFillColor( 1, 1, 0 )
panel.item4.x = -230
panel.item4.y = -50
panel:insert( panel.item4 )
local bottoneex = widget.newButton
{
--defaultFile = "play.png",
label="ESCI",
labelColor =
{
default = { 1, 1, 0, 255 },
},
font = native.systemFont,
fontSize = 40,
emboss = false,
textonly = true,
onEvent = provap
}
bottoneex.x = -250
bottoneex.y = 50
panel:insert( bottoneex )
function apripanel()
panel:show()
panel:toFront()
end
function provap()
panel:hide()
end
My target is to build a slide menu by adding to this panel several menu items. How can I achieve that? I tried putting a button ( textonly = true ) but I am not sure...what if I add displaytext (as I did)? How can I add listeners to texts?
Sorry for silly questions but I am quit new to this language! :)
Thanks a lot for your help! Ubaldo
Hiding the menu works for me, maybe you use an old version of newPanel.lua, this should work for you.
main.lua
widget = require("widget")
require("newPanel")
BG = display.newRect(160,240,300,460)
BG:setFillColor( 0.1, 0.1, 0.1, 1.0 )
local panel = widget.newPanel
{
location = "left",
onComplete = panelTransDone,
width = display.contentWidth*0.8,
height = display.contentHeight,
speed = 500,
inEasing = easing.outBack,
outEasing = easing.outCubic
}
panel.background = display.newRect( 0, 0, 320, 480 )
panel.background:setFillColor( 0.3, 0.3, 0.3, 0.9 )
panel:insert( panel.background )
panel.item1 = display.newText( "Item1", 0, 0, native.systemFontBold, 10 )
panel.item1:setFillColor( 1, 1, 0 )
panel:insert( panel.item1 )
local function ButtonEvent1(e)
if ( "ended" == e.phase ) then
print( "Button 1" )
panel:show()
panel:toFront()
end
end
local function ButtonEvent2(e)
if ( "ended" == e.phase ) then
print( "Button 1" )
panel:hide()
end
end
local B1 = widget.newButton({left = 100,top = 200,id = "button1",label = "Show",onEvent = ButtonEvent1})
local B2 = widget.newButton({left = 100,top = 300,id = "button2",label = "Hide",onEvent = ButtonEvent2})
newPanel.lua
function widget.newPanel( options )
local customOptions = options or {}
local opt = {}
opt.location = customOptions.location or "top"
local default_width, default_height
if ( opt.location == "top" or opt.location == "bottom" ) then
default_width = display.contentWidth
default_height = display.contentHeight * 0.33
else
default_width = display.contentWidth * 0.33
default_height = display.contentHeight
end
opt.width = customOptions.width or default_width
opt.height = customOptions.height or default_height
opt.speed = customOptions.speed or 500
opt.inEasing = customOptions.inEasing or easing.linear
opt.outEasing = customOptions.outEasing or easing.linear
if ( customOptions.onComplete and type(customOptions.onComplete) == "function" ) then
opt.listener = customOptions.onComplete
else
opt.listener = nil
end
local container = display.newContainer( opt.width, opt.height )
if ( opt.location == "left" ) then
container.anchorX = 1.0
container.x = display.screenOriginX
container.anchorY = 0.5
container.y = display.contentCenterY
elseif ( opt.location == "right" ) then
container.anchorX = 0.0
container.x = display.actualContentWidth
container.anchorY = 0.5
container.y = display.contentCenterY
elseif ( opt.location == "top" ) then
container.anchorX = 0.5
container.x = display.contentCenterX
container.anchorY = 1.0
container.y = display.screenOriginY
else
container.anchorX = 0.5
container.x = display.contentCenterX
container.anchorY = 0.0
container.y = display.actualContentHeight
end
function container:show()
local options = {
time = opt.speed,
transition = opt.inEasing
}
if ( opt.listener ) then
options.onComplete = opt.listener
self.completeState = "shown"
end
if ( opt.location == "top" ) then
options.y = display.screenOriginY + opt.height
elseif ( opt.location == "bottom" ) then
options.y = display.actualContentHeight - opt.height
elseif ( opt.location == "left" ) then
options.x = display.screenOriginX + opt.width
else
options.x = display.actualContentWidth - opt.width
end
transition.to( self, options )
end
function container:hide()
local options = {
time = opt.speed,
transition = opt.outEasing
}
if ( opt.listener ) then
options.onComplete = opt.listener
self.completeState = "hidden"
end
if ( opt.location == "top" ) then
options.y = display.screenOriginY
elseif ( opt.location == "bottom" ) then
options.y = display.actualContentHeight
elseif ( opt.location == "left" ) then
options.x = display.screenOriginX
else
options.x = display.actualContentWidth
end
transition.to( self, options )
end
return container
end
I'm developing an android app with the libav and I'm trying decode a 3gp with code below:
#define simbiLog(...) __android_log_print(ANDROID_LOG_DEBUG, "simbiose", __VA_ARGS__)
...
AVCodec *codec;
AVCodecContext *c = NULL;
int len;
FILE *infile, *outfile;
uint8_t inbuf[AUDIO_INBUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
AVPacket avpkt;
AVFrame *decoded_frame = NULL;
simbiLog("inbuf size: %d", sizeof(inbuf) / sizeof(inbuf[0]));
av_register_all();
av_init_packet(&avpkt);
codec = avcodec_find_decoder(AV_CODEC_ID_AMR_NB);
if (!codec) {
simbiLog("codec not found");
return ERROR;
}
c = avcodec_alloc_context3(codec);
if (!c) {
simbiLog("Could not allocate audio codec context");
return ERROR;
}
int open = avcodec_open2(c, codec, NULL);
if (open < 0) {
simbiLog("could not open codec %d", open);
return ERROR;
}
infile = fopen(inputPath, "rb");
if (!infile) {
simbiLog("could not open %s", inputPath);
return ERROR;
}
outfile = fopen(outputPath, "wb");
if (!outfile) {
simbiLog("could not open %s", outputPath);
return ERROR;
}
avpkt.data = inbuf;
avpkt.size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);
int iterations = 0;
while (avpkt.size > 0) {
simbiLog("iteration %d", (++iterations));
simbiLog("avpkt.size %d avpkt.data %X", avpkt.size, avpkt.data);
int got_frame = 0;
if (!decoded_frame) {
if (!(decoded_frame = avcodec_alloc_frame())) {
simbiLog("out of memory");
return ERROR;
}
} else {
avcodec_get_frame_defaults(decoded_frame);
}
//below the error, but it isn't occur on first time, only in 4th loop interation
len = avcodec_decode_audio4(c, decoded_frame, &got_frame, &avpkt);
if (len < 0) {
simbiLog("Error while decoding error %d frame %d duration %d", len, got_frame, avpkt.duration);
return ERROR;
} else {
simbiLog("Decoding length %d frame %d duration %d", len, got_frame, avpkt.duration);
}
if (got_frame) {
int data_size = av_samples_get_buffer_size(NULL, c->channels, decoded_frame->nb_samples, c->sample_fmt, 1);
size_t* fwrite_size = fwrite(decoded_frame->data[0], 1, data_size, outfile);
simbiLog("fwrite returned %d", fwrite_size);
}
avpkt.size -= len;
avpkt.data += len;
if (avpkt.size < AUDIO_REFILL_THRESH) {
memmove(inbuf, avpkt.data, avpkt.size);
avpkt.data = inbuf;
len = fread(avpkt.data + avpkt.size, 1, AUDIO_INBUF_SIZE - avpkt.size, infile);
if (len > 0)
avpkt.size += len;
simbiLog("fread returned %d", len);
}
}
fclose(outfile);
fclose(infile);
avcodec_close(c);
av_free(c);
av_free(decoded_frame);
but I'm getting the follow log and error:
inbuf size: 20488
iteration 1
avpkt.size 3305 avpkt.data BEEED40C
Decoding length 13 frame 1 duration 0
fwrite returned 640
fread returned 0
iteration 2
avpkt.size 3292 avpkt.data BEEED40C
Decoding length 13 frame 1 duration 0
fwrite returned 640
fread returned 0
iteration 3
avpkt.size 3279 avpkt.data BEEED40C
Decoding length 14 frame 1 duration 0
fwrite returned 640
fread returned 0
iteration 4
avpkt.size 3265 avpkt.data BEEED40C
Error while decoding error -1052488119 frame 0 duration 0
the audio file I'm trying decode:
$ avprobe blue.3gp
avprobe version 0.8.6-6:0.8.6-1ubuntu2, Copyright (c) 2007-2013 the Libav developers
built on Mar 30 2013 22:23:21 with gcc 4.7.2
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'blue.3gp':
Metadata:
major_brand : 3gp4
minor_version : 0
compatible_brands: isom3gp4
creation_time : 2013-09-19 18:53:38
Duration: 00:00:01.52, start: 0.000000, bitrate: 17 kb/s
Stream #0.0(eng): Audio: amrnb, 8000 Hz, 1 channels, flt, 12 kb/s
Metadata:
creation_time : 2013-09-19 18:53:38
thanks a lot!
EDITED
I read on ffmper documentation about the method avcodec_decode_audio4 the follow:
#warning The input buffer, avpkt->data must be FF_INPUT_BUFFER_PADDING_SIZE larger than the actual read bytes because some optimized bitstream readers read 32 or 64 bits at once and could read over the end.
#note You might have to align the input buffer. The alignment requirements depend on the CPU and the decoder.
and I see here a solution using posix_memalign, to android i founded a similar method called memalign, so i did the change:
removed:
uint8_t inbuf[AUDIO_INBUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
inserted:
int inbufSize = sizeof(uint8_t) * (AUDIO_INBUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE);
uint8_t *inbuf = memalign(FF_INPUT_BUFFER_PADDING_SIZE, inbufSize);
simbiLog("inbuf size: %d", inbufSize);
for (; inbufSize >= 0; inbufSize--)
simbiLog("inbuf position: %d index: %p", inbufSize, &inbuf[inbufSize]);
I'm getting the correct memory sequence position, but the error not changed.
A piece of outpout:
inbuf position: 37 index: 0x4e43d745
inbuf position: 36 index: 0x4e43d744
inbuf position: 35 index: 0x4e43d743
inbuf position: 34 index: 0x4e43d742
inbuf position: 33 index: 0x4e43d741
inbuf position: 32 index: 0x4e43d740
inbuf position: 31 index: 0x4e43d73f
inbuf position: 30 index: 0x4e43d73e
inbuf position: 29 index: 0x4e43d73d
inbuf position: 28 index: 0x4e43d73c
inbuf position: 27 index: 0x4e43d73b
inbuf position: 26 index: 0x4e43d73a
inbuf position: 25 index: 0x4e43d739
inbuf position: 24 index: 0x4e43d738
inbuf position: 23 index: 0x4e43d737
inbuf position: 22 index: 0x4e43d736
inbuf position: 21 index: 0x4e43d735
inbuf position: 20 index: 0x4e43d734
inbuf position: 19 index: 0x4e43d733
You are trying to decode without demuxing.