Русский
Русский
English
Статистика
Реклама

Camera

7 вещей, которые нужно проработать, прежде чем запускать OpenShift в продакшн

24.12.2020 16:21:15 | Автор: admin
Взрывной рост использования контейнеров на предприятиях впечатляет. Контейнеры идеально совпали с ожиданиями и потребностями тех, кто хочет снизить затраты, расширить свои технические возможности и продвинуться вперед по пути agile и devops. Контейнерная революция открывает новые возможности и для тех, кто подзадержался с обновлением ИТ-систем. Контейнеры и Kubernetes это абсолютно и принципиально новый способ управления приложениями и ИТ-инфраструктурой.



В отличие от предыдущего и столь же революционного перехода от голого железа к виртуальным машинам, контейнеры кардинально сокращают избыточность программного стека и меняют саму природу управления операционными системами на предприятии.

Многие решают ускорить переход на контейнеры с помощью Red Hat OpenShift Container Platform, ведущей отраслевой Kubernetes-платформы для корпоративного сектора. Это решение автоматически берет на себя множество задач первого дня и предлагает лучшую Kubernetes-экосистему на базе единой, тщательно протестированной и высоко защищенной платформы. Это наиболее комплексное и функциональное решение для предприятий, которое содержит всё необходимое для начала работы и устраняет массу технических барьеров и сложностей при построении Kubernetes-платформы.

Тем не менее, OpenShift это не волшебная палочка, которая решает все проблемы сама. Да, благодаря своим возможностям, эта платформа способна принести и приносит своим заказчикам массу пользы и быстро окупается, но при условии, что на момент ее запуска у вас есть хорошо продуманный план. Чтобы добиться успеха, надо тщательно проработать семь областей, прежде чем приступать к переносу каких-либо рабочих нагрузок на OpenShift.

1. Стандартизация правил именования и метаданных


В компьютерных науках есть только две трудные вещи: аннулирование кэша и именование сущностей.
Фил Карлтон (Phil Karlton)

У всякой сущности в OpenShift и Kubernetes есть свое имя. И у каждого сервиса должно быть свое DNS-имя, единственное ограничение здесь правила именования DNS. А теперь представьте, что монолитное приложение разложилось на 100500 отдельных микросервисов, каждый с собственной базой данных. И да, в OpenShift всё является либо иерархическим, связанным, либо должно соответствовать шаблону. Так что именовать придется массу и массу всего. И если заранее не подготовить стандарты, это получится настоящий Дикий Запад.

Вы уже распланировали схему реализации сервисов? Допустим, это будет одно большое пространство имен, например, databases, в котором все будут размещать свои базы данных. OK, и даже допустим, что все так и будут делать, но потом-то они начнут размещать свои кластеры Kafka в своих собственных пространствах имен. Да, а нужно ли заводить пространство имен middleware? Или лучше назвать его messaging? И как обычно, в какой-то момент появляются ребята, которые всегда идут своим путем и считают себя особенными, и говорят, что им нужны собственные пространства имен. И слушайте, у нас же в организации 17 подразделений, может надо приделать ко всем пространствам имен наши стандартные префиксы подразделений?

Прежде чем пускать что-либо в продакшн, продумайте стандарты именования и сопоставления сэкономите массу времени и сил, если сделаете это заранее. Введите стандарты на всё. Причем, здесь важно не столько их качество, сколько наличие, целостность и выполнение.

Другая мегаполезная вещь это метаданные. Стандартизируйте, какие активы хотите отслеживать, и убедитесь, что на соответствующих ресурсах прописаны нужные метаданные. Начните с рекомендованных меток. Например, аннотация support_email в метаданных пространства имен может сэкономить драгоценное время при выходе на техподдержку второго уровня в случае серьезного отказа. Кроме того, метаданные можно использовать, чтобы сократить имена ресурсов до вменяемой длинны, а не прописывать туда всю необходимую информацию через дефис. Привлеките всех, от архитекторов приложений до ИТ-эксплуатантов, устройте мозговой штурм и просчитайте наперед, что может здесь понадобиться, чтобы иметь продуманные стандарты к моменту запуска OpenShift.

2. Стандартизация корпоративных базовых образов


Одна из ключевых фишек контейнеров это возможность миксовать и подбирать все составляющие программного стека. Можно, конечно, взять любимую разновидность ОС и строить все на ней, но действуя подобным образом организация упускает огромные возможности. Ведь что по-настоящему круто в контейнерных образах? Многослойность. Вы можете снять с разработчиков массу критичных задач и решать их за счет стандартизации образов.

Возьмем, к примеру, базовое java-приложение. Ваши разработчики вряд ли ошибутся с выбором OpenJDK, а вот с управлением уязвимостями, обновлением библиотек и прочими вопросами ИТ-гигиены вполне могут. Мы все знаем, что бизнес-задачи зачастую решаются ценой технических компромиссов, вроде намеренного использования старых версий Java. К счастью, такие задачи легко автоматизируются и управляются на уровне предприятия. Вы по-прежнему может использовать базовые образы вендора, но одновременно задавать и контролировать свои циклы обновления, создавая собственные базовые образы.

Возвращаясь к примеру выше, допустим, разработчикам нужна Java 11, а вам, соответственно, надо, чтобы они всегда использовали самую последнюю версию Java 11. Тогда вы создаете корпоративный базовый образ (registry.yourcompany.io/java11), используя в качестве отправной точки базовый образ от вендора ОС (registry.redhat.io/ubi8/openjdk-11). А когда этот базовый образ обновляется, вы автоматом помогаете разработчикам задействовать последние обновления. К тому же, таким образом реализуется уровень абстракции, позволяющий бесшовно дополнять стандартный образ необходимыми библиотеками или Linux-пакетами.

3. Стандартизация проверок работоспособности и готовности


Контроль исправности, он нужен практически везде. Считается, что для человека достаточно ежегодного медосмотра. Исправность приложений надо проверять, понятно, гораздо чаще, и контролировать две ключевые вещи:

  • Запущено ли приложение (health check работоспособность).
  • Готово ли приложение (readiness check готовность).

Существует масса и других метрик, чтобы облегчить мониторинг приложений, но вот эти две это основа основ не только мониторинга, но и масштабирования. Работоспособность обычно определяется наличием сетевого подключения и способностью узла, на котором выполняется приложение, отозваться на запрос. Что касается готовности, то здесь уже каждое приложение должно реагировать на запросы по своим стандартам. Например, запуск приложения с очень низкими задержками может сопровождаться длительным обновлением кэша или прогревом JVM. И соответственно, пауза между ответами Запущено и Готово может достигать нескольких минут. А вот, например, для stateless REST API с реляционной базой данных эти ответы будут приходить одновременно.

Самое главное в этих проверках не отходить от сугубо двоичной логики. Запущено значит запущено, без всяких там как бы запущено. Готово значит готово, и никаких градаций, вроде на такие запросы готово отвечать, а на такие нет. Принцип простой: всё или ничего.

Второй аспект таких проверок это стандартизация. Как проверить готовность? Если у вас нет стандартов, то даже такой простой вопрос может стать настоящим кошмаром для мониторинга. Просто сравните, как разошлись друг от друга стандарты Quarkus и стандарты Spring Boot. А ведь никто этого не хотел, но со стандартами всегда так. Единственная разница в том, что теперь ваша организация сама имеет власть разрабатывать и вводить стандарты.
Примечание на полях. Не изобретайте свои стандарты. Просто найдите и используйте какой-нибудь готовый.

4. Стандартизация логов


Продолжая тему мониторинга, отметим, что сочетание недорогих хранилищ и решений класса big data породило на предприятиях нового монстра тотальное журналирование. Раньше это были неструктурированные и архаичные консольным логи, которые жили недолго и создавались от случая к случаю. Теперь норовят запротоколировать всё подряд и выстроить датасайнс с машинным обучением, чтобы самым революционным образом оптимизировать операции и мониторинг. Увы, надо признать очевидное: любые попытки начать собирать логи сотен приложений, не имея при этом абсолютно никаких стандартов и даже не задумываясь о них, неизменно приводят к бессмысленным и непомерным тратам на инструменты для управления логами и трансформации данных лишь для того, чтобы только начать работу. То есть еще до того, как вы поймете, что сообщения Выполнен переход или Этот блок сработал вряд имеют хоть какое-то отношение к вашим операциям.

Стандартизировать надо структуру. Повторимся: целостность стандартов важнее их правильности. Вы должны быть способы написать отдельный лог-парсер для каждого приложения, которое есть на предприятии. Да, это будут сугубо штучные, не тиражируемые вещи. Да, у вас будет куча исключений, которые нельзя контролировать, особенно для коробочных приложений. Но не выплескивайте ребенка вместе с водой, уделите внимание деталям: например, временная метка в каждом логе должна отвечать соответствующему стандарту ISO; сам вывод должен быть в формате UTC с точностью до 5-го знака в микросекундах (2018-11-07T00:25:00.07387Z). Уровни журнала должны быть оформлены CAPS-ом и там должны быть элементы TRACE, DEBUG, INFO, WARN, ERROR. В общем, задайте структуру, а уже затем разбирайтесь с подробностями.

Стандартизация структуры заставит всех придерживаться одних правил и использовать одни и те же архитектурные шаблоны. Это верно для логов как приложений, так и платформ. И не отклоняйтесь от готового решения без крайней нужды. EFK-стек (Elasticsearch, Fluentd и Kibana) платформы OpenShift должен быть в состоянии обработать все ваши сценарии. Он ведь вошел в состав платформы не просто так, и при ее обновлении это еще одна вещь, о которой не надо беспокоиться.

5. Переход на GitOps


Одна из главных прелестей OpenShift заключается в том, что здесь всё буквально: всё в конечном является либо конфигурацией, либо кодом, а значит, может контролироваться через систему управления версиями. Это позволяет революционизировать способы доставки и избавиться от бюрократии при запуске в продакшн.

В частности, традиционную схему на основе тикетов можно полностью заменить на модель с pull-запросами git. Допустим, владелец приложения хочет подкорректировать выделяемые приложению ресурсы после реализации в нем новых функций, например, увеличить память с 8 до 16 ГБ. В рамках традиционной схемы разработчику для этого надо создать тикет и ждать, пока кто-то другой выполнит соответствующую задачу. Этим кем-то другим чаще всего оказывается ИТ-эсплуатант, который лишь вносит ощутимую задержку в процесс реализации изменений, никак не повышая ценность этого процесса, или хуже того, навешивая на этот процесс лишние дополнительные циклы. В самом деле, у эсплуатанта есть два варианта действий. Первое: он рассматривает заявку и решает ее выполнить, для чего входит в продакшн-среду, вносит затребованные изменения вручную и перезапускает приложение.
Помимо времени на проведение самой работы здесь возникает и дополнительная задержка, поскольку у эксплуатанта, как правило, всегда есть целая очередь заявок на выполнение. Кроме того, возникает риск человеческой ошибки, например, ввод 160 ГБ вместо 16 ГБ. Второй вариант: эксплуатант ставит заявку под сомнение и тем самым запускает цепную реакцию по выяснению причин и последствий запрашиваемых изменений, да так, что иногда уже приходится вмешиваться начальству.

Теперь посмотрим, как это делается в GitOps. Запрос на изменения попадает в репозиторий git и превращается в pull-запрос. После чего разработчик может выставить этот pull-запрос (особенно, если это изменения в продакшн-среде) для утверждения причастными сторонами. Таким образом, специалисты по безопасности могут подключиться уже на ранней стадии, и всегда есть возможность отследить последовательность изменений. Стандарты в этой области можно внедрять программно, используя соответствующие средства в инструментальной цепочке CI/CD. После того, как его утвердили, pull-запрос версионируется и легко поддается аудиту. Кроме того, его можно протестировать в среде pre-production в рамках стандартного процесса, полностью устранив риск человеческой ошибки.

Как видим, изменения радикальные. Но в новинку они будут не столько разработчикам, которым не привыкать к системам управления версиями, сколько системным администраторам и специалистам по безопасности. Но как только те вникнут в новую парадигму и оценят ее силу и простоту, идея зайдет на ура.

6. Схемы приложений (Blueprints)


Переход от монолитных приложений к микросервисам усиливает роль шаблонов проектирования (паттернов) приложений. В самом деле, типичное монолитное приложение не особо поддается классификации. Как правило, там есть и REST API, и пакетная обработка, и событиями оно управляется. HTTP, FTP, kafka, JMS и Infinispan? Да пожалуйста, а еще оно одновременно работает с тремя разными базами данных. И как прикажете создавать схему, когда здесь намешана целая куча шаблонов интеграции корпоративных приложений? Да никак.

Но если разложить такое монолитное приложение на отдельные части, то шаблоны выделяются гораздо проще и легче. Допустим, теперь это четыре отдельных приложения, и в них используются следующие шаблоны:

  • REST API для управления данными в СУБД.
  • Пакетная обработка, которая проверят FTP-сервер на предмет обновления данных и отправляет их в топик kafka.
  • Camelадаптер, берущий данные из этого kafka-топика и отправляющий их в REST API
  • REST API, которые выдают обобщенную информацию, собираемую из Data Grid, которая действует как конечный автомат.

Итак, теперь у нас есть схемы, а схемы уже можно стандартизировать. REST API должны отвечать стандартам Open API. Пакетные задания будут управляться как пакетные задания OpenShift. Интеграции будут использовать Camel. Схемы можно создавать для API, для пакетных заданий, для AI/ML, для multicast-приложений, да для чего угодно. А затем уже можно определять, как развертывать эти схемы, как их конфигурировать, какие шаблоны использовать. Имея такие стандарты, не надо будет каждый раз изобретать колесо, и вы сможете лучше сфокусироваться на действительно важных задачах, вроде создания нового бизнес-функционала. Проработка схем может показаться пустой тратой времени, но затраченные усилия сторицей вернутся в будущем.

7. Подготовьтесь к API


Вместе с микросервисной архитектурой приходят и API. Ими тоже придется управлять и лучше подготовиться к этому заранее.

Во-первых, здесь опять понадобятся стандарты. В качестве отправной точки можно взять стандарты Open API, но придется углубиться в дебри. Хотя здесь важно соблюсти баланс и не впасть в чрезмерную зарегламентированность с кучей ограничений. Посмотрите на эти вопросы: когда новая сущность создается с помощью POST, что надо возвращать, 201 или 200? разрешается ли обновлять сущности с помощью POST, а не PUT? В чем разница между 400-ми и 500-ми ответами? примерно такой уровень детализации вам нужен.

Во-вторых, понадобится сервисная сетка service mesh. Это реально сильная вещь и со временем она станет неотъемлемой частью Kubernetes. Почему? Потому что трафик рано или поздно превратится в проблему, и вам захочется управлять им как внутри дата-центра (т.н. трафик восток-запад), так и между дата-центром и внешним по отношению к нему миром (север-юг). Вам захочется вытащить из приложений аутентификацию и авторизацию и вывести их на уровень платформы. Вам понадобятся возможности Kiali по визуализации трафика внутри service mesh, а также сине-зеленые и канареечные схемы развертывания приложений, или, к примеру, динамический контроль трафика. В общем, service mesh без вопросов входит в категорию задач первого дня.

В-третьих, вам понадобится решение для централизованного управления API. Вам захочется иметь одно окно для поиска и повторного использования API. Разработчикам понадобится возможность зайти в магазин API, найти там нужный API и получить документацию по его использованию. Вы захотите единообразно управлять версиями и deprecation-ами. Если вы создаете API для внешних потребителей, то такое решение может стать конечной точкой север-юг во всем, что касается безопасности и управления нагрузкой. 3Scale даже может помочь с монетизицией API. Ну и рано или поздно ваше руководство захочет получить отчет, отвечающий на вопрос Какие у нас есть API?.

В заключение особо отметим, что хотя определение областей для стандартизации и документирование корпоративных стандартов уже сами по себе могут выглядеть пугающе, львиная доля усилий уходит не на это, а на мониторинг и контроль соблюдения стандартов. Мощная смесь организационной энтропии и вполне естественного нежелания конфликтовать с коллегами с самого начала работают против стандартов. Борьба распадается на бессчетное количество крошечных и порой незаметных сражений: здесь отсутствует требуемая метка, а это имя хоть и не полностью, но все же в достаточной мере отвечает стандарту. Стандарты обычно умирают смертью от тысячи порезов, и об этом в организации мало кто знает, если знает вообще. В каком-то смысле стандарты это как физические упражнения: никто не хочет потеть и напрягаться, но все знают, что без них невозможна долгая и здоровая жизнь.

Однако, надежда есть, и она заключается в автоматизации. Любой из перечисленных выше стандартов можно внедрить с помощью автоматизации. Процесс GitOps может проверять, что во всех соответствующих yaml-файлах присутствуют все требуемые метки и аннотации. Процесс CI/CD может контролировать соблюдение стандартов на корпоративные образы. Все может быть кодифицировано, проверено и приведено в соответствие. Кроме того, автоматизацию можно доработать, когда вы вводите новые стандарты или меняете существующие. Безусловное преимущество стандартизации через автоматизацию заключается в том, что компьютер не избегает конфликтов, а просто констатирует факты. Следовательно, при достаточной проработанности и инвестициях в автоматизацию, платформа, в которую вы вкладываете столько средств сегодня, может принести гораздо больший возврат инвестиций в будущем в виде повышения производительности и стабильности.
Подробнее..

Фото из Android смартфона в Qt Widgets

28.02.2021 14:11:42 | Автор: admin

Если у вас нет времени читать или информация известна, окончательный код получения полноразмерного изображения из камеры Android-смартфона расположен в конце статьи.

Описание проблемы

Если вы пишете кросс-платформенное приложение, то для получения изображения из камеры для ПК можно воспользоваться классом QCamera, пример для работы с которым описан в документации Qt.

В соответствии с указанным примером мы добавляем в .pro файл

QT += multimedia multimediawidgets

Далее создаём виджет в своей программе, отображающий изображение из веб-камеры и сохраняющий его в QPixmap или QImage для дальнейшего использования.

Когда возникает задача сделать то-же самое на Android, то выясняется, что multimediawidgets не поддерживаются данной ОС и камера снимать и сохранять снимки будет, но что она отображает в текущий момент будет загадкой, т.к. QCameraViewfinder использует multimediawidgets и на Android не отображает ничего. Дальнейший поиск решения проблемы приводит к двум вариантам решения:

  1. использовать QML и написать свой Qt Quick-элемент, выполняющий эту функцию, затем состыковать его остальной частью приложения на Qt Widgets, С++;

  2. использовать приложение по-умолчанию Android-смартфона для получения фотографии, затем обработать её в своём приложении.

Рассмотрим первый вариант

Если вы С++ программист Qt Widgets, то очередное эпизодическое углубление в QML займёт у вас время, добавим к этому время на написание Qt Quick-элемента, стыковки этого элемента с С++ кодом, отладки написанного кода. Если вы не профессионал в QML получается долго и сложно.

Рассмотрим второй вариант

В Android-смартфоне уже есть приложение по-умолчанию, прекрасно выполняющее нужную функцию, нужно им просто воспользоваться, применив Java-вызовы (JNI Java Native Interface) из С++ кода при помощи QtAndroid. Выглядит проще. Полностью работающего кода в интернете я не нашёл, и, изучив опыт других, опираясь на документацию разработчка на Android написал собственный.

Как это сделать

Если у вас нет времени читать или информация известна, окончательный код получения полноразмерного изображения из камеры Android-смартфона расположен в конце статьи.

Прочитав статью Получить фотографии на Android я сделал вывод, что применив данный метод можно получить или миниатюру изображения в виде массива пикселей или сохранить изображение в файл. Поискав готовые решения, я нашёл на GitHub подходящий код, который должен был выполнить нужную мне задачу. При его проверке, оказалось, что он устарел и теперь приводит к FileUriExposedException исключению, причины возникновения которого описаны в вышеуказанной ссылке на статью.

Чтобы разобраться самостоятельно, как оно работает, начнём с простой задачи, не требующей обращения к файлам и множества Java-вызовов получению миниатюры.

Получение миниатюры

Начнём с .pro файла.

Он должен содержать следующие строки для поддержки Android.

android {    QT       +=androidextras}

Для получения результата нам понадобится класс, унаследованный от QAndroidActivityResultReceiver. Если требуется, чтобы объект нашего класса высылал изображение при помощи сигнала, то он также должен быть унаследован от любого класса Qt, имеющего базовый класс QObject.

Заголовочный файл (.h) класса имеет вид:

#ifndef CAMSHOT_H#define CAMSHOT_H#include <QObject>#include <QString>#include <cstring>#include <QImage>#include <QDebug>#include <QtAndroid>#include <QAndroidActivityResultReceiver>#include <QAndroidParcel>class CamShot : public QObject, public QAndroidActivityResultReceiver{    Q_OBJECTpublic:    CamShot(QObject *parent = nullptr):QObject(parent),QAndroidActivityResultReceiver(){}        static const int RESULT_OK = -1;     static const int REQUEST_IMAGE_CAPTURE = 1;    static const int REQUEST_TAKE_PHOTO = REQUEST_IMAGE_CAPTURE;    void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data)  override;    static QImage camThumbnailToQImage(const QAndroidJniObject &data);public slots:    void aMakeShot();signals:    void createNew(const QImage &img);};#endif // CAMSHOT_H

Заголовочный файл (.cpp) класса имеет вид:

QImage CamShot::camThumbnailToQImage(const QAndroidJniObject &data){    QAndroidJniObject bundle = data.callObjectMethod("getExtras","()Landroid/os/Bundle;");    qDebug()<<"bundle.isValid() "<<bundle.isValid()<<bundle.toString();    QAndroidJniObject bundleKey = QAndroidJniObject::fromString("data");    const QAndroidJniObject aBitmap (data.callObjectMethod("getParcelableExtra", "(Ljava/lang/String;)Landroid/os/Parcelable;", bundleKey.object<jstring>()));    qDebug()<<"aBitmap.isValid() "<<aBitmap.isValid()<<aBitmap.toString();    jint aBitmapWidth = aBitmap.callMethod<jint>("getWidth");    jint aBitmapHeight = aBitmap.callMethod<jint>("getHeight");    QAndroidJniEnvironment env;    const int32_t aBitmapPixelsCount = aBitmapWidth * aBitmapHeight;    jintArray pixels = env->NewIntArray(aBitmapPixelsCount);    jint aBitmapOffset = 0;    jint aBitmapStride = aBitmapWidth;    jint aBitmapX = 0;    jint aBitmapY = 0;    aBitmap.callMethod<void>("getPixels","([IIIIIII)V", pixels, aBitmapOffset, aBitmapStride, aBitmapX, aBitmapY, aBitmapWidth, aBitmapHeight);    jint *pPixels = env->GetIntArrayElements(pixels, nullptr);    QImage img(aBitmapWidth, aBitmapHeight, QImage::Format_ARGB32);    int lineSzB = aBitmapWidth * sizeof(jint);    for (int i = 0; i < aBitmapHeight; ++i){        uchar *pDst = img.scanLine(i);        const uchar *pSrc = reinterpret_cast<const uchar*>(pPixels + aBitmapWidth * i + aBitmapWidth);        memcpy(pDst, pSrc, lineSzB);    }    env->DeleteLocalRef(pixels); //env->ReleaseIntArrayElements(pixels, pPixels, 0); отвязывает указатель на данные массива от массива, а надо удалить сам массив, поэтому DeleteLocalRef.    return img;}void CamShot::aMakeShot() {    QAndroidJniObject action = QandroidJniObject::fromString("android.media.action.IMAGE_CAPTURE");    //Если необходимо указать Java-класс (не аргумент функции), то указывается полное имя класса (точки-разделители заменяются на "/"), например  "android/content/Intent", "java/lang/String".    //Если аргумент функции Java-объект, то писать имя класса начиная с "L" и ";" в конце, например "Landroid/content/Intent ;", "Ljava/lang/String;".    //Если примитивный тип или массив, то указываются соответствующие символы без разделителей, например "V" (void) или "[IIIIIII" (массив jint, и 6 jint за ним)    //Символы, соответствия примитивны типам:    QAndroidJniObject intent=QAndroidJniObject("android/content/Intent","(Ljava/lang/String;)V", action.object<jstring>());    QtAndroid::startActivity(intent, REQUEST_IMAGE_CAPTURE, this);}void CamShot::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data){    if ( receiverRequestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK )    {        const QImage thumbnail (camThumbnailToQImage(data));        if (!thumbnail.isNull())            emit createNew(thumbnail);    }}

Разберём приведённый код

Краткие правила указания аргументов в JNI-вызовах

  1. если необходимо указать имя Java-класса (не в качестве аргумента Java-функции), то указывается полное имя класса (точки-разделители заменяются на "/"), например "android/content/Intent", "java/lang/String";

  2. если аргумент функции Java-объект, то писать имя его класса начиная с "L" и ";" в конце, например "Landroid/content/Intent;", "Ljava/lang/String;";

  3. если примитивный тип или массив, то указываются соответствующие сигнатуры (символы без разделителей), например "V" (void), "I" (jint) или "[IIIIIII" (массив jint, и 6 jint за ним);

  4. сигнатуры примитивных типов:

    C/C++

    JNI

    Java

    Signature

    uint8_t/unsigned char

    jboolean

    bool

    Z

    int8_t/char/signed char

    jbyte

    byte

    B

    uint16_t/unsigned short

    jchar

    char

    C

    int16_t/short

    jshort

    short

    S

    int32_t/int/(long)

    jint

    int

    I

    int64_t/(long)/long long

    jlong

    long

    J

    float

    jfloat

    float

    F

    double

    jdouble

    double

    D

    void


    void

    V

  5. сигнатуры массивов:

    JNI

    Java

    Signature

    jbooleanArray

    bool[]

    [Z

    jbyteArray

    byte[]

    [B

    jcharArray

    char[]

    [C

    jshortArray

    short[]

    [S

    jintArray

    int[]

    [I

    jlongArray

    long[]

    [L

    jfloatArray

    float[]

    [F

    jdoubleArray

    double[]

    [D

    jarray

    type[]

    [Lfully/qualified/type/name;

    jarray

    String[]

    [Ljava/lang/String;


    Чтобы получить доступ к элементам массива, необходимо использовать JNI-методы объекта класса QAndroidJniEnvironment, например такие как: NewIntArray, GetIntArrayElements, DeleteLocalRef GetArrayLength,GetObjectArrayElement, SetObjectArrayElement, и т.д.

Подробнее можно прочитать в презентации (pdf) Practical Qt on Android JNI qtcon.

В заголовочном файле class CamShot содержит:

  1. значения констант, взятых их документации разработчка Android (так код короче и меньше Java-вызовов);

  2. переопределение абстрактного метода void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override; в который будет передаваться Java-объект класса Intent с миниатюрой изображения;

  3. статический метод
    static QImage camThumbnailToQImage(const QAndroidJniObject &data);
    извлекающий из Java-объекта класса Intent Java-объект класса Bitmap, копирующий пиксели в массив пкселей (32-битных значений) и построчно копирующий эти пиксели в QImage;

  4. общедоступный слот
    void aMakeShot();
    вызывающий операцию по фотографированию изображения и получению его миниатюры;

  5. сигнал
    void createNew(const QImage &img);
    высылающий полученную миниатюру потребителю.

В методе void aMakeShot() создаётся Java-объект Intent в который передаётся строка со значением, указывающим, что необходимо сделать произвести захват изображения. После этого сформированное действие (Intent) отправляется на исполнение (Activity).

В процессе выполнения действия будет запущено приложение по-умолчанию для фотографирования. Как только фотография будет сделана и подтверждена пользователем, будет произведён вызов виртуального метода handleActivityResult, в котором осуществляется проверка: является ли выполненное действие запрошенным и успешно выполненным. Если да, то вызовем статический метод camThumbnailToQImage получения изображения QImage из Java-объекта класса Bitmap и при успешном результате отправим полученное изображение потребителю сигналом Qt.

Рассмотрим статический метод
static QImage camThumbnailToQImage(const QAndroidJniObject &data) override;

Интересующее нас изображение передаётся в блоке дополнительных данных Java-объекта класса Intent и является Java-объектом класса Bundle, чтобы его получить нужно воспользоваться методом объекта Intent:
Bundle getExtras()

В Bundle хранятся ассоциативные пары <ключ-строка>:<значение>. В статье получить фотографии на Android указан ключ, по которому располагается миниатюра. Это строка "data".

Получим Java-объект класса Bitmap по ключу, воспользовавшись методом объекта Intent:
T getParcelableExtra (String name)

Cобрать из Bitmap QImage сразу не получится, т.к. у Bitmap нет указателя на данные изображения вместе с заголовком его формата. Поэтому получим его размер (ширину и высоту) в пискселях и создадим QImage аналогичного размера для копирования значений пикселей в него.

Для переноса значений пикселей из Bitmap в QImage воспользуемся методом объекта Bitmap:
void getPixels (int[] pixels, int offset, int stride, int x, int y, int width, int height)

Для этого понадобится создать линейный массив значений пикселей
jintArray pixels = env->NewIntArray(aBitmapPixelsCount);
После того, как пиксели будут скопированы, получим указатель на начало массива, который можно использовать в C++ коде:
jint *pPixels = env->GetIntArrayElements(pixels, nullptr);
Затем в цикле построчно скопируем значения пикселей из массива в изображение Qimage. По завершению копирования освобождаем память, выделенную под массив значений пикселей
env->DeleteLocalRef(pixels);
и возвращаем результат в виде QImage.

Отлично. Миниатюра изображения получена.

Получение полноразмерного изображения

Для получения полноразмерного изображения необходимо воспользоваться классом FileProvider, чтобы получить разделяемый Uri для файла фотоснимка. Обращаю ваше внимание, что у Android, по крайней мере, их два:

  1. androidx.core.content.FileProvider;

  2. android.support.v4.content.FileProvider.

Первый самый современный, не поддерживается Qt, а для использования второго необходимо настроить среду QtCerator:

Установить дополнительные репозитории

Главное меню (сверху) Инструменты Параметры Устройства вкладка Android вкладка SDK ManagerРазвернуть элемент списка Инструменты в список Extras Android Support Repository - поставить флажок установить и нажить на кнопку Применить справа.

Заменить автогенерируемые файлы настройки сборки для Android собственными

Перейти на боковой панели QtCreator на вкладку Проекты. В левой области окна Сборка и запуск Сборка. Тогда в правой области окна Build Android APK Create Templates. В появившемся диалоговом окне установить флажок Копировать файлы Gradle в каталог Android, нажать на кнопку Завершить:

Добавить каталог со своими настройками сборки в проект

В каталоге с исходными кодами вашего приложения появится каталог android, который необходимо добавить в проект.

Настроить в файле проекта отключаемую возможность поддержки Android

Если приложение кросс-платформенное и предполагается компиляция не только на Android, то в .pro файле необходимо добавить директиву android: перед каждым добавленным файлом:

android {    QT       +=androidextras}#  DISTFILES += \android:    android/AndroidManifest.xml \android:    android/build.gradle \android:    android/gradle/wrapper/gradle-wrapper.jar \android:    android/gradle/wrapper/gradle-wrapper.properties \android:    android/gradlew \android:    android/gradlew.bat \android:    android/res/values/libs.xml \    todo.txt

Отредактировать AndroidManifest.xml

Отредактировать файл AndroidManifest.xml в android/AndroidManifest.xml, добавив в секцию после

</activity><!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices ->

текст:

<provider android:name="android.support.v4.content.FileProvider" android:authorities="org.qtproject.example.qsketch.fileprovider" android:grantUriPermissions="true" android:exported="false"><meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/></provider>

Создать файл с указанием каталога совместного использования с другими приложениями

Это нужно для того, чтобы приложение фотографирования по-умолчанию могло передать нашему приложению файл.

В каталоге сборки, там где находится автогенерируемый файл AndroidManifest.xml внутри каталога res рядом с каталогом values, создать каталог xml, а в нём файл file_paths.xml ( /abin/AndroidManifest.xml) ( /abin/res/xml/file_paths.xml). В созданный файл поместить следующие строки:

<?xml version="1.0" encoding="utf-8"?><paths xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android"><external-files-path name="shared" path="shared/" /></paths>

где shared/ имя каталога в каталоге файлов нашего приложения

Добавить компонент, содержащий FileProvider в сборку

Отредактировать файл android/build.gradle, добвив в секцию dependencies текст:

compile'com.android.support:support-v4:25.3.1'

секция целиком выглядит так:

dependencies {implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])compile'com.android.support:support-v4:25.3.1'}

Данная инструкция сработает, если был установлен Android Support Repository.

Настройка выполнена на основании статьи Sharing Files on Android or iOS from or with your Qt App - Part 4 и подобных статей на эту тему.

Заголовочный файл (.h) класса имеет вид:

#ifndef CAMSHOT_H#define CAMSHOT_H#include <QObject>#include <QImage>#include <QString>#include <QDebug>#include <QtAndroid>#include <QAndroidActivityResultReceiver>#include <QAndroidParcel>#include "auxfunc.h"class CamShot : public QObject, public QAndroidActivityResultReceiver{    Q_OBJECTpublic:    static const int RESULT_OK = -1;    static const int REQUEST_IMAGE_CAPTURE = 1;    static const int REQUEST_TAKE_PHOTO = REQUEST_IMAGE_CAPTURE;    enum ImgOrientation {ORIENTATION_UNDEFINED = 0, ORIENTATION_NORMAL = 1, ORIENTATION_FLIP_HORIZONTAL = 2, ORIENTATION_ROTATE_180 = 3, ORIENTATION_FLIP_VERTICAL = 4, ORIENTATION_TRANSPOSE = 5,                       ORIENTATION_ROTATE_90 = 6, ORIENTATION_TRANSVERSE = 7, ORIENTATION_ROTATE_270 = 8};    void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override;    static QImage aBitmapToQImage(const QAndroidJniObject &aBitmap);    static QImage camThumbnailToQImage(const QAndroidJniObject &data);    ImgOrientation needRotateAtRightAngle();    QImage camImageToQImage();    static void applyOrientation(QImage &img, const ImgOrientation &orientation);    explicit CamShot(QObject *parent = nullptr):QObject(parent),QAndroidActivityResultReceiver(){}    ~CamShot();private:        QAndroidJniObject tempImgURI;    QAndroidJniObject tempImgFile;    QAndroidJniObject tempImgAbsPath;    bool _thumbnailNotFullScaleRequested;public slots:    void aMakeShot(const bool &thumbnailNotFullScale = false);signals:    void createNew(const QImage &img);};#endif // CAMSHOT_H

Заголовочный файл (.cpp) класса имеет вид:

QImage CamShot::aBitmapToQImage(const QAndroidJniObject &aBitmap){    if (!aBitmap.isValid())        return QImage();    jint aBitmapWidth = aBitmap.callMethod<jint>("getWidth");    jint aBitmapHeight = aBitmap.callMethod<jint>("getHeight");    QAndroidJniEnvironment env;    const int32_t aBitmapPixelsCount = aBitmapWidth * aBitmapHeight;    jintArray pixels = env->NewIntArray(aBitmapPixelsCount);    jint aBitmapOffset = 0;    jint aBitmapStride = aBitmapWidth;    jint aBitmapX = 0;    jint aBitmapY = 0;    aBitmap.callMethod<void>("getPixels","([IIIIIII)V", pixels, aBitmapOffset, aBitmapStride, aBitmapX, aBitmapY, aBitmapWidth, aBitmapHeight);    jint *pPixels = env->GetIntArrayElements(pixels, nullptr);    QImage img(aBitmapWidth, aBitmapHeight, QImage::Format_ARGB32);    int lineSzB = aBitmapWidth * sizeof(jint);    for (int i = 0; i < aBitmapHeight; ++i){        uchar *pDst = img.scanLine(i);        const uchar *pSrc = reinterpret_cast<const uchar*>(pPixels + aBitmapWidth * i + aBitmapWidth);        memcpy(pDst, pSrc, lineSzB);    }    env->DeleteLocalRef(pixels); //env->ReleaseIntArrayElements(pixels, pPixels, 0); отвязывает указатель на данные массива от массива, а надо удалить сам массив, поэтому DeleteLocalRef.    return img;}QImage CamShot::camThumbnailToQImage(const QAndroidJniObject &data){    //Получить дополнительный данные    QAndroidJniObject bundle = data.callObjectMethod("getExtras","()Landroid/os/Bundle;");    qDebug()<<"bundle.isValid() "<<bundle.isValid()<<bundle.toString();    //Создать объект типа jstring (строка Java) со значением "data" - ключ для извлечения из ассоциативного контейнера пар <ключ, значение> миниатюры - объекта типа Bitmap (Java)    QAndroidJniObject bundleKey = QAndroidJniObject::fromString("data");    //Получить по ключу "data" дополнительный данные: миниатюру в виде объекта Bitmap    const QAndroidJniObject aBitmap (data.callObjectMethod("getParcelableExtra", "(Ljava/lang/String;)Landroid/os/Parcelable;", bundleKey.object<jstring>()));    qDebug()<<"aBitmap.isValid() "<<aBitmap.isValid()<<aBitmap.toString();    return aBitmapToQImage(aBitmap);}QImage CamShot::camImageToQImage(){    QAndroidJniObject bitmap = QAndroidJniObject::callStaticObjectMethod("android/graphics/BitmapFactory","decodeFile","(Ljava/lang/String;)Landroid/graphics/Bitmap;",tempImgAbsPath.object<jobject>());    qDebug()<<"bitmap.isValid() "<<bitmap.isValid()<<bitmap.toString();    QImage img = aBitmapToQImage(bitmap);    //Удаление файла    if (tempImgFile.isValid())        tempImgFile.callMethod<jboolean>("delete");    return img;}CamShot::ImgOrientation CamShot::needRotateAtRightAngle(){    //Вызов конструктора объекта    QAndroidJniObject exifInterface = QAndroidJniObject("android/media/ExifInterface","(Ljava/lang/String;)V",                                                     tempImgAbsPath.object<jstring>());    qDebug() << __FUNCTION__ << "exifInterface.isValid()=" << exifInterface.isValid();    QAndroidJniObject TAG_ORIENTATION = QAndroidJniObject::getStaticObjectField<jstring>("android/media/ExifInterface", "TAG_ORIENTATION");    qDebug() << __FUNCTION__ << "TAG_ORIENTATION.isValid()=" << TAG_ORIENTATION.isValid()<<TAG_ORIENTATION.toString();    const jint orientation = exifInterface.callMethod<jint>("getAttributeInt","(Ljava/lang/String;I)I",TAG_ORIENTATION.object<jstring>(),static_cast<jint>(ORIENTATION_UNDEFINED));    return static_cast<ImgOrientation>(orientation);}void CamShot::applyOrientation(QImage &img, const ImgOrientation &orientation){    switch (orientation){    case ORIENTATION_UNDEFINED:    case ORIENTATION_NORMAL:        break;    case ORIENTATION_FLIP_HORIZONTAL:{        img = img.mirrored(true, false);        break;    }    case ORIENTATION_ROTATE_180:        Aux::rotateImgCW180(img);        break;    case ORIENTATION_FLIP_VERTICAL:{        img = img.mirrored(false, true);        break;    }    case ORIENTATION_TRANSPOSE:{        img = img.mirrored(true, false);        Aux::rotateImgCW270(img);        break;    }    case ORIENTATION_ROTATE_90:        Aux::rotateImgCW90(img);        break;    case ORIENTATION_TRANSVERSE:{        img = img.mirrored(true, false);        Aux::rotateImgCW90(img);        break;    }        break;    case ORIENTATION_ROTATE_270:        Aux::rotateImgCW270(img);        break;    }}void CamShot::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data){    if ( receiverRequestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK )    {        if (_thumbnailNotFullScaleRequested){            const QImage thumbnail (camThumbnailToQImage(data));            if (!thumbnail.isNull())                emit createNew(thumbnail);            return;        }        const ImgOrientation orientation = needRotateAtRightAngle();        QImage image (camImageToQImage());        if (!image.isNull()){            applyOrientation(image, orientation);            emit createNew(image);        }    }}void CamShot::aMakeShot(const bool &thumbnailNotFullScale) {    QAndroidJniObject action = QAndroidJniObject::fromString("android.media.action.IMAGE_CAPTURE");    //Вызов конструктора объекта    QAndroidJniObject intent=QAndroidJniObject("android/content/Intent","(Ljava/lang/String;)V",                                                 action.object<jstring>());    qDebug() << __FUNCTION__ << "intent.isValid()=" << intent.isValid();    _thumbnailNotFullScaleRequested = thumbnailNotFullScale;    if (thumbnailNotFullScale) {        //Для получения миниатюры        QtAndroid::startActivity(intent, REQUEST_IMAGE_CAPTURE, this);        return;    }    //Для получения изображения в файл    QAndroidJniObject context = QtAndroid::androidContext();    QString contextStr (context.toString());    qDebug() <<"Context: "<<contextStr;    //Каталог для хранения файлов приложения    QAndroidJniObject extDir = context.callObjectMethod("getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;", NULL);    qDebug() << __FUNCTION__ << "extDir.isValid()=" << extDir.isValid()<<extDir.toString();    //Абсолютный путь к каталогу для хранения файлов приложения в виде строки    QAndroidJniObject extDirAbsPath = extDir.callObjectMethod("getAbsolutePath","()Ljava/lang/String;");    //Добавим имя каталога для совместного использования файлов этого приложения другими приложениями. См. /res/xml/file_paths.xml    extDirAbsPath = QAndroidJniObject::fromString(extDirAbsPath.toString() + "/shared");    const QString extDirAbsPathStr = extDirAbsPath.toString();    qDebug() << __FUNCTION__ << "extDirAbsPath.isValid()=" << extDirAbsPath.isValid()<<extDirAbsPathStr;    //Создать объект типа Файл для разделяемого каталога    QAndroidJniObject sharedFolder=QAndroidJniObject("java.io.File","(Ljava/lang/String;)V",                                                      extDirAbsPath.object<jstring>());    qDebug() << __FUNCTION__ << "sharedFolder.isValid()=" << sharedFolder.isValid()<<sharedFolder.toString();    const jboolean sharedFolderCreated = sharedFolder.callMethod<jboolean>("mkdirs");    Q_UNUSED(sharedFolderCreated);    //Прежде чем пытаться создать файл с заданным именем, нужно проверить файл с этим именем на существование    //Предположительно путь к этому файлу    QAndroidJniObject suggestedFilePath = QAndroidJniObject::fromString(extDirAbsPathStr+"/"+"_tmp.jpg");    qDebug() << __FUNCTION__ << "suggestedFilePath.isValid()=" << suggestedFilePath.isValid()<<suggestedFilePath.toString();    //Создать объект типа Файл    //Вызов конструктора объекта    QAndroidJniObject tempImgFile=QAndroidJniObject("java.io.File","(Ljava/lang/String;)V",                                                 suggestedFilePath.object<jstring>());    qDebug() << __FUNCTION__ << "fileExistsCheck.isValid()=" << tempImgFile.isValid()<<tempImgFile.toString();    //Удаление файла, если он существует    if (tempImgFile.isValid()){        const jboolean deleted = tempImgFile.callMethod<jboolean>("delete");        Q_UNUSED(deleted);    }    //Создать физический файл для записи в него изображения по указанному пути    const jboolean fileCreated = tempImgFile.callMethod<jboolean>("createNewFile");    Q_UNUSED(fileCreated);    //Абсолютный путь к созданному файлу в виде строки    tempImgAbsPath = tempImgFile.callObjectMethod("getAbsolutePath","()Ljava/lang/String;");    qDebug() << __FUNCTION__ << "tempImgAbsPath.isValid()=" << tempImgAbsPath.isValid()<<tempImgAbsPath.toString();    //Получить authority для fileprovider    const QString contextFileProviderStr ("org.qtproject.example.qsketch.fileprovider");    const char androidFileProvider  [] = "android/support/v4/content/FileProvider";    //const char androidxFileProvider [] = "androidx/core/content/FileProvider"; - не поддерживается Qt    /*QAndroidJniObject*/ tempImgURI = QAndroidJniObject::callStaticObjectMethod(androidFileProvider, "getUriForFile", "(Landroid/content/Context;Ljava/lang/String;Ljava/io/File;)Landroid/net/Uri;",                                                                             context.object<jobject>(), QAndroidJniObject::fromString(contextFileProviderStr).object<jstring>(), tempImgFile.object<jobject>());    qDebug() << __FUNCTION__ << "tempImgURI.isValid()=" << tempImgURI.isValid()<<tempImgURI.toString();    //Получить значение константы MediaStore.EXTRA_OUTPUT    QAndroidJniObject MediaStore__EXTRA_OUTPUT        = QAndroidJniObject::getStaticObjectField("android/provider/MediaStore", "EXTRA_OUTPUT", "Ljava/lang/String;");    qDebug() << "MediaStore__EXTRA_OUTPUT.isValid()=" << MediaStore__EXTRA_OUTPUT.isValid();    //Добавить URI путь файла для записи в него данных к задаче    intent.callObjectMethod("putExtra","(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;",MediaStore__EXTRA_OUTPUT.object<jstring>(), tempImgURI.object<jobject>());    qDebug() << __FUNCTION__ << "intent.isValid()=" << intent.isValid();    QtAndroid::startActivity(intent, REQUEST_IMAGE_CAPTURE, this);}

Так-же статические методы класса Aux для поворота изображения.

Заголовочный файл (.h) класса Aux имеет вид:

#ifndef AUXFUNC_H#define AUXFUNC_H#include <QImage>#include <QColor>#include <QPainter>#include <QMatrix>#include <QSize>#include <QPoint>class Aux{public:    static void resizeCenteredImg(QImage *image, const QSize &newSize, const QColor bgColor);    static void rotateImg(QImage &img, qreal degrees);    static void rotateImgCW90(QImage &img);    static void rotateImgCW180(QImage &img);    static void rotateImgCW270(QImage &img);};#endif // AUXFUNC_H

Файл исходного кода (.cpp) класса Aux имеет вид:

void Aux::resizeCenteredImg(QImage *image, const QSize &newSize, const QColor bgColor){    if (image->size() == newSize)        return;    const QSize szDiff = newSize - image->size();    QImage newImage(newSize, QImage::Format_ARGB32);    newImage.fill(bgColor);    QPainter painter(&newImage);    painter.drawImage(QPoint(szDiff.width()/2, szDiff.height()/2), *image);    *image = newImage;}void Aux::rotateImg(QImage &img, qreal degrees){    QPoint center = img.rect().center();    QMatrix matrix;    matrix.translate(center.x(), center.y());    matrix.rotate(degrees);    img = img.transformed(matrix, Qt::SmoothTransformation);}void Aux::rotateImgCW90(QImage &img){    const int w = img.width();    const int h = img.height();    const int maxDim = std::max(w, h);    resizeCenteredImg(&img, QSize(maxDim, maxDim), Qt::white);    rotateImg(img, 90);    resizeCenteredImg(&img, QSize(h, w), Qt::white);}void Aux::rotateImgCW180(QImage &img){    rotateImg(img, 180);}void Aux::rotateImgCW270(QImage &img){    const int w = img.width();    const int h = img.height();    const int maxDim = std::max(w, h);    resizeCenteredImg(&img, QSize(maxDim, maxDim), Qt::white);    rotateImg(img, 270);    resizeCenteredImg(&img, QSize(h, w), Qt::white);}

Указанный метод позволяет получить за один раз или миниатюру или полноразмерное изображение.

Результат зависит от переданного логического параметра thumbnailNotFullScale. Если он равен логической единице, то будет получена миниатюра, если логическому нулю, то полноразмерное изображение. Попытка получить миниатюру при запросе сохранения полноразмерного изображения в файл приведёт к исключению в JNI-вызовах.

Если миниатюра всегда ориентирована правильно, то полноразмерное изображение направлено в одну строну и его необходимо поворачивать. Информацию о необходимых преобразованиях можно получить из exif-свойств изображения при помощи ExifInterface. В обнаруженных в интернете Java-примерах преобразование к нормальной ориентации производится в Java-коде, в случае с Qt нет смысла мучить себя трудно отлаживаемыми, громоздкими JNI-вызовами и проще выполнить все необходимые преобразования в Qt.

Подробнее..

Olympus уходит с рынка цифровых камер

29.06.2020 14:12:48 | Автор: admin

Печально, но факт. В пресс-релизе от 24 июня компания Olympus подтвердила намерение о продаже своих подразделений, занимающихся производством цифровых камер своим партнерам JIP (Japan Industrial Partners). Тем самым, которые выкупили у Sony подразделение VAIO.

История бренда Olympus началась в 1919 году, причем впервые на рынке он появился как Takachiho Seisakusho по названию пика Takachiho (горного массива Takamagahara в префектуре Gunma в Японии). С тех пор компания несколько раз переживала ребрендинг, а современный вариант привычной для нас марки был создан в 2003 году.

image

Semi-Olympus I. Источник фотографии: olympus-global.com
Интересный факт: С момента выхода первой фотокамеры Semi-Olympus I (копия немецкой Balda Baldax) прошло уже 84 года.
Одной из важнейших побед в современной истории компании была коллаборация с Panasonic. 5 августа 2008 года был представлен формат матрицы Micro 4/3 (Micro Four Thirds). Новый стандарт позволил не только уменьшить рабочий отрезок в 2 раза, но и избавиться от зеркала в конструкции. В том же году на выставке Photokina-2008 Olympus и Panasonic продемонстрировали первый прототип беззеркальной камеры. Именно Micro 4/3 стал основоположником всех современных беззеркалок.

Что произошло?


Конкуренция со стороны смартфонов и необходимость сокращения расходов. Именно эти факторы стали причиной отказа компании от дальнейшего развития в области цифровых камер. Последние три года менеджмент компании фиксировал убытки, связанные с общемировым падением спроса на фотокамеры. Тем не менее, на этом более чем столетний опыт работы над оптическим оборудованием не будет утерян. Свои усилия Olympus сосредоточит на разработке и выпуске медицинского оборудования.

Что же это значит для обычного владельца камеры линейки OM-D или другой камеры Olympus формата Micro 4/3? Вполне допускаю, что на этом их история закончится, равно как и история бренда объективов M.ZUIKO.


Лично для меня как владельца OM-D EM10 Mark II эта новость стала весьма неприятной. Несмотря на неудобное главное меню, аппарат превосходно кастомизируется. На любую кнопку или диск управления можно повесить практически любую функцию. Идеальная цветопередача (лучше только у премиальных брендов, например, Leica) и шустрая работа почти всегда давали мне именно ту картинку, которую я ожидал получить. К примеру, большая часть фотографий к статье Археологи цифрового века были сделаны именно на эту камеру.

Стоит ли прямо сейчас бежать в магазин и выкупать камеры, которые пока еще есть в магазинах? Вопрос остается открытым. Надо четко понимать, что, возможно, скоро не будет обновлений ПО, возникнет нехватка запасных частей или будет невозможно починить камеру в официальном сервисе. С другой стороны, если JIP, как Olympus уверяет в пресс-релизе, продолжит работу над брендом и обеспечит поддержку выпущенных камер, то эти опасения не сбудутся. Ожидается, что сделка будет закрыта в сентябре этого года.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru