Последние записи
- Рандомное слайдшоу
- Событие для произвольной области внутри TImage
- Удаление папки с файлами
- Распечатка файла
- Преобразовать массив байт в вещественное число (single)
- TChromium (CEF3), сохранение изображений
- Как в Delphi XE обнулить таймер?
- Изменить цвет шрифта TextBox на форме
- Ресайз PNG без потери прозрачности
- Вывод на печать графического файла
Интенсив по Python: Работа с API и фреймворками 24-26 ИЮНЯ 2022. Знаете Python, но хотите расширить свои навыки?
Slurm подготовили для вас особенный продукт! Оставить заявку по ссылке - https://slurm.club/3MeqNEk
Online-курс Java с оплатой после трудоустройства. Каждый выпускник получает предложение о работе
И зарплату на 30% выше ожидаемой, подробнее на сайте академии, ссылка - ttps://clck.ru/fCrQw
25th
Апр
Как работать с графикой на канве в среде Дельфи. Урок 1–2
Понятия и методы работы с графикой в среде Дельфи для начинающих (полезно для создающих первые игры). Все сопровождается подробными примерами «космической стрелялки»…
Как работать с графикой на канве в среде Дельфи. Урок 1–2
Владимир Дегтярь
DeKot degvv@mail.ru
Графика в Delphi (немного теории). Урок 1
Операционная среда WINDOWS является графической средой и для вывода графической информации на экран или принтер использует функции GDI (Graphics Devices Interface – Интерфейс графических устройств). GDI – функции являются аппаратно–независимыми, поэтому взаимодействие приложений (в том числе и созданных в среде Delphi) с аппаратными устройствами компьютера осуществляются через драйвера устройств и через специальную структуру данных – называемой контекстом отображения (дисплейный контекст – DC (Display Context)). Контекст отображения содержит основные характеристики устройств вывода графической информации, а также инструменты для рисования (шрифт, перо и кисть).
Система Delphi предлагает специальные классы, упрощающие использование графических средств:
- TCanvas – для контекста отображения
- TFont – для шрифта
- TPen – для пера
- TBrush – для кисти
Связанные с этими классами объекты получают соответствующие свойства – canvas, font, pen, brush, которые уже как объекты, в свою очередь, имеют ряд своих свойств.
Для работы с рисунками или изображениями Delphi предлагает классы: TGraphic, TPicture, TImage, TBitMap, TJpegImage, TShape, TIcon, TMetaFile и др. Основной класс для связанных с рисованием графических операций – это TCanvas. С помощью его свойств и методов можно рисовать на поверхности визуальных объектов, которые включают в себя этот класс и имеют свойство сanvas. Для выполнения различных графических операций используются типы TPoint и TRect, описываемые следующим образом (см. листинг 1):
ЛИСТИНГ–1
…
TPoint = record // задание координат точки
X : LongInt;
Y : LongInt;
TRect = record // определение прямоугольной области
Left : Integer;
Top : Integer;
Right : Integer;
Bottom : Integer;
…
или
…
TRect = record // определение прямоугольной области
TopLeft : TPoint;
BottomRight : TPoint;
…
также
…
TRect := Bounds ( X,Y, Width, Height : Integer );
X, Y – координаты левого верхнего угла области ;
Width, Height – ширина и высота прямоугольной области;
Одним из основных объектов для рисования является – поверхность рисования (она же холст или канва) – объект класса TCanvas. У холста есть ряд свойств и методов для отображения графической информации, перемещения графических объектов по поверхности рисования, копирования изображений и/или их отдельных областей, вывода текстовой информации.
Наиболее частыми применяемыми методами отображения (вывода) графики являются:
- Canvas < рисование геометрических фигур (примитивы) > Arc ( дуга), Pie ( сектор), Ellipse ( эллипс,
круг), Rectangle (прямоугольник) и другие
- Canvas.Draw – вывод графической информации
- Canvas.StretchDraw – вывод графической информации с изменением масштаба
- Canvas.CopyRect – копирование графической области
- Canvas. TextOut – вывод текстовой информации
Кроме этого еще используются методы загрузки графических объектов из файлов, из других компонентов или объектов.
Самое простое приложение с выводом графики. Урок 2
Создадим новый проект в среде Delphi «File => New => Application» (при запуске Delphi новый проект создается автоматически). Cразу же сохраним проект – File => SaveAll. На первый запрос – сохраняем модуль под именем предложенным Delphi – Unit 1. По второму запросу изменим имя проекта с Project 1 на Lesson 1. Delphi в новом проекте создает объект Form1 и в редакторе кода модуля Unit 1 появляется заготовка кода (см. рис.1):
Рис. 1 Создаем новый проект.
В Object Inspector в свойствах формы Form1 изменим заголовок Caption на «Урок по графике №1» и выставим размеры окна формы: Top (100*), Left (230), Width (700), Height (575), ClientWidth (700), ClientHeight (540).
* почему именно эти цифры разберем позже
Теперь с помощью методов графических примитивов нарисуем, что–либо на форме. В Object Inspector перейдем на вкладку Events (События) и «кликнем» дважды по событию OnPaint(). В редакторе кода появится шаблон процедуры обработчика события On Paint (см. рис.2):
Рис. 2. Создание обработчика OnPaint()
Теперь запишем в этой процедуре следующий код (см. листинг 2):
ЛИСТИНГ–2
…
with form1.canvas do begin
pen.color:= clred; // цвет пера
rectangle(350, 50, 550, 100); // рисуем прямоугольник с координатами
// верхнего левого угла
// x1=350, y1=50 и правого нижнего x2=550, y2=100
pen.color:= clgreen;
pen.width:= 4; // ширина пера
brush.color:= clskyblue; // цвет заполнения фигуры
ellipse(60, 100, 250, 400) // эллипс, вписанный в прямоугольник
end;
Запустите проект (Run или F9 ) и посмотрите результат. Конечно, это слишком простой проект, поэтому усложним нашу задачу с использованием рисунков находящихся в файлах (формат файлов .bmp). Графические файлы, которые нам понадобятся для последующих проектов, находятся в папке data в соответствующих папках проектов (см. ресурсы к статье).
Далее, выведем на форму изображение звездолета (файл ‘ship1.bmp’ ) на фоне звездного неба (файл ‘star1.bmp’ ). В файле ‘ship1’ два изображения звездолета (спрайты – они нам понадобятся для организации движения звездолета), файл ‘star1’ используется для создания фона и имеет размер 700 х 540 (под эти размеры и установлены размеры окна формы через Object Inspector). Нам также понадобятся объекты типа TBitMap: BufFon (буфер для загрузки фона из файла ‘star1.bmp’ ), BufSpr (буфер для загрузки спрайтов из файла ‘ship1.bmp’ ), BufPic (буфер для загрузки рисунка одного из спрайтов из BufSpr), Buffer (общий буфер для объединения всех рисунков с последующим выводом на форму).
Размеры BufFon и BufSpr устанавливаются в соответствии с размерами изображений при загрузке. Размер общего буфера Buffer устанавливаем равным BufFon, а размер BufPic – равен размеру одного спрайта, что в общем случае определяется следующим образом:
BufPic.Width := round ( BufSpr.Width / n );
BufPic.Height:= round ( BufSpr.Height / m );
где n – кол–во спрайтов в горизонтальном ряду изображений в файле ‘sprite’,
m – кол–во рядов с изображением спрайтов в файле.
Инициализацию буферов проведем в процедуре OnCreate() формы (см. рис.3):
Рис. 3. Инициализация буферов
Для вывода одного спрайта через BufPic** создаем процедуру копирования спрайта из BufSpr в BufPic методом CopyRect (см. листинг 3):
ЛИСТИНГ–2
…
procedure DrawShip1 ( i: byte); // загрузка одного спрайта в буфер рисунка
begin
BufPic.Canvas.CopyRect(bounds(0, 0, BufPic.Width, BufPic.Height),
BufSpr.Canvas,bounds( i * 66, 0, BufPic.Width,
BufPic.Height));
end;
** на Canvas BufPic в область с координатами левого верхнего угла X= 0 и Y = 0, шириной и высотой соответствующие размерам буфера BufPic копируем изображение спрайта из области с область с координатами левого верхнего угла X= i * 66 и Y = 0, шириной и высотой соответствующие размерам буфера BufPic. В координате Х цифра 66 соответствует ширине одного спрайта. В переменной i передается номер спрайта (0 – 1-й, 1 – 2-й).
Вывод изображений производим аналогично предыдущему примеру (рисование прямоугольника и эллипса) в процедуре OnPaint(). Необходимо ввести переменные xs1 и ys1 (координаты вывода звездолета). Процедура DrawShip1(0) c параметром 0 выводит первый спрайт в буфер рисунка BufPic. Далее выводим фон и спрайт на канву дополнительного буфера Buffer и затем из него выводим все на форму. Удалите из процедуры код предыдущего примера и вставьте следующий (см. листинг 3):
ЛИСТИНГ–3
…
procedure DrawShip1 (i: byte); // загрузка одного спрайта в буфер рисунка
begin
BufPic.Canvas.CopyRect(bounds(0, 0, BufPic.Width, BufPic.Height),
BufSpr.Canvas, bounds(i * 66, 0, BufPic.Width,
BufPic.Height));
end;
procedure Tform1.FormPaint(sender: tobject);
var xs1, ys1: integer; // координаты звездолета SHIP1
begin
xs1:= 250; ys1:= 466;
DrawShip1(0);
Buffer.canvas.draw(0, 0, BufFon); // выводим фон в общий буфер
Buffer.canvas.draw(xs1, ys1, BufPic); // выводим рисунок спрайта поверх
// фона в общий
Buffer.canvas.draw(0, 0, Buffer); // вывод обеих рисунков (общего буфера)
// на форму
end;
После запуска проекта и компиляции получаем следующую картинку (см. рис.4):
Рис. 4. Тестовый проект звездолета
Заключение
Мы получили статическое изображение и теперь в последующих уроках создадим движущиеся графические объекты. Но для начала познакомимся с основными принципами получения «эффекта» движения объектов (папка Lesson1***).
*** Перед запуском в среде Дельфи скопируйте в папку с проектом папку data с графическими файлами.
Можно использовать Уроки в любых некоммерческих целях с указанием автора и ссылкой на
По всем вопросам обращайтесь на форум www.programmersforum.ru или на E-mail.
Рассматриваемые в данной статье проекты полностью приведены в ресурсах к статье на http://www.programmersforum.ru в разделе «Журнал клуба программистов. Первый выпуск».
Статья из первого выпуска “журнала ПРОграммистов”.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Как работать с графикой на канве в среде Дельфи. Урок 1–2
25th
Быстрое преобразование Фурье. Практика использования. Часть 2
Традиционно, измерительный прибор представляет собой автономное специализированное устройство, к которому подключаются анализируемые входные сигналы и на выходе которого имеется некий результат. Причем внутренняя архитектура остается неизменной, в отличие от виртуальных приборов. И если для изменения функциональности в первом, требуется существенная переработка схемы и конструкции, то для вторых достаточно изменить программу. Продолжая наш цикл по практике использования быстрого преобразования Фурье [1], сегодня мы с вами научим наш виртуальный прибор работать не только с низкочастотной частью диапазона и аудиоустройствами, но и с высокоскоростной платой сбора данных и для удобства принимать команды управления основными режимами программы с пульта… Данная статья рассчитана в помощь программистам и инженерам-разработчикам в области цифровой обработки сигналов (DSP).
Быстрое преобразование Фурье. Практика использования. Часть 2
DSP – (Digital Signal Processing) преобразование сигналов, представленных в цифровой форме
АЦП – аналого–цифровой преобразователь
Зоны Найквиста – зеркальные отображения спектра при использовании частот выше половины частоты дискретизации (частоты Найквиста)
БПФ/ДПФ – быстрое / дискретное преобразование Фурье.
Зачем-же все это нужно? Как мы уже знаем, виртуальные приборы (ВП) благодаря гибкости в их построении все больше вытесняют дорогостоящие автономные аппаратные решения, таких как осциллографы, спектроанализаторы и др. При этом пользователь не ограничен в выборе средств для анализа и обработки информации, что сводится лишь к изменению программного обеспечения. Приведем пример: вы купили «имиджевый» осциллограф, используете его до поры до времени… и вот настает момент, когда меняется задача и вам понадобился анализ спектра АЦП. Снова затраты? Рассмотрим подробнее…
Краткий экскурс…
В настоящее время основополагающим принципом цифровой обработки сигналов (ЦОС) является преобразование аналогового сигнала в цифровой на промежуточной частоте (перенос спектра). Это позволяет исключить такие недостатки аналогового способа формирования квадратурных сигналов, как: невысокие стабильность и нелинейность, неидентичность каналов, смещение фазы и трудности последующей фильтрации. Кроме того, это несколько снижает жесткие требования к элементной базе по частотным характеристикам. Но правило остается, чем выше быстродействие аппаратной логики, тем больший диапазон наблюдения можно охватить, не прибегая к различного рода программным ухищрениям и ограничениям при выборе частот преобразования по зонам Найквиста, дискретности, тактовой частоты и даже питания и многое–многое другое.
Промышленные платы Hammerhead от Bittware с успехом справляются с этими условиями. Аппаратную часть виртуального оборудования подключают к промышленному компьютеру, как правило, через шины USB или PCI. Первый вариант не требует вскрытия компьютера, а второй дает обмен на порядок быстрее (спецификация USB3.0 увы пока редко встречается). Задача верхнего уровня сводится к окончательному анализу полученных данных с сочетанием сервисного удобства персонального компьютера (ПК). Кроме того, можно выделить лишь небольшое количество фирм, производящих комплекты для создания виртуального оборудования для работы с DSP в области ВЧ/СВЧ. Наиболее крупные из них – Analog Devices, Bittware, Kontron, National Instruments и Texas Instruments.
Аппаратная часть. Краткое описание объекта
Аппаратной основой (см. рис.2-3) виртуального прибора служит 8–ми слотовое шасси VD3U от компании Kontron [2] с промышленным контроллером CP–306, с установленными периферийными cPCI (Compact PCI) платами Hammerhead фирмы Bittware формата 3U* с четырьмя сигнальными процессорами SHARC ADSP–21160 от Analog Devices. Платы обеспечивают прием и обработку в реальном времени (единицы микросекунд) данных от 4–х каналов АЦП с частотой выборки 32 МГц каждый. Питание обеспечивает встроенный в шасси источник +3.3V/+5V/+12V/–12V. Обмен данными осуществляется по PCI шине. Программная оболочка виртуального прибора в комплексе с аппаратной служит для оценки работоспособности, уровня шумов, джиттера, динамического диапазона модулей АЦП.
* 1U – принятая высота корпуса 44 мм
Предпосылки реализации ПО
Как же получить данные с DSP платы? Все достаточно просто. Для доступа к периферии производитель предоставляет набор драйверов и библиотеку <Hil.dll> или <Hil32v60.dll> в зависимости от версии, подробное описание API которой вы можете узнать в документации [2, 3], а полный список экспортируемых функций вы можете просмотреть в модуле <dsp.pas> (см. ресурсы к статье). Нам-же понадобятся следующие:
- dsp21k_open_by_id() – массив указателей на область памяти каждого процессора
- dsp21k_reset_bd() – реинициализация программы в процессоре
- dsp21k_load_exe() – загрузка прошивки в процессор
- dsp21k_start() – старт программы в процессоре
- dsp21k_dl_int() – запись значения в область памяти процессора
- dsp21k_ul_int() – считывание значения с области памяти процессора
- dsp21k_ul_32s() – считывание значения с области памяти процессора (32 бит)
В основу работы программы положен все тот-же алгоритм быстрого преобразования Фурье (БПФ), применяемый к полученным отсчетам сигнала на нижнем уровне и переданными для обработки наверх.
Таким образом, уже можем определить основные требования к нашему виртуальному прибору:
- возможность загрузки прошивки в сигнальные процессоры платы Hammerhead
- визуализация первичных отсчетов, полученных промышленной платой с модуля АЦП
- построение спектра (БПФ/ДПФ) в реальном времени и с возможностью сохранения дампов
- возможность распечатки отображаемых (сохраненных) данных
- управление основными режимами с пульта ДУ
Практика. Разработка ПО и средства отладки
Итак, приступим к основной задаче. Для работы нам понадобится следующее:
- IDE среда разработки Borland Delphi 5–7 (использована для разработки ПО верхнего уровня)
- промышленная плата Hammerhead и модулем АЦП
- генератор сигналов типа Г4–301 или любой другой до 100 МГц
- USB приемник дистанционного управления из материала [4] (см. рис.4)
- любой ИК пульт дистанционного управления для тестирования
Работа с DSP платой
Внешний вид программы реализован через скиновую систему, более подробно (см. исходники). Рассмотрим основные ключевые моменты по доступу к данным… Прежде всего, необходимо подключить и проинициализировать библиотеку для работы с драйвером (см. листинг 1):
ЛИСТИНГ–1
получаем доступ к драйверу
…
function LoadMyDll: bool;
var HLib: THandle;
NewLib: Boolean;
begin
result:= false;
Hlib := LoadLibrary(’Hil.dll’); // осуществляем динамическое подключение–
NewLib:= (HLib<>0);
if not NewLib then HLib:= LoadLibrary(’Hil32v60.dll’);
if Hlib = 0 then exit;
dsp21k_open_by_id := GetProcAddress(HLib, ‘dsp21k_open_by_id’);
dsp21k_close := GetProcAddress(HLib, ‘dsp21k_close’);
if NewLib then begin
dsp21k_load_exe := GetProcAddress(HLib, ‘dsp21k_load_exe’);
dsp21k_dl_exe := dsp21k_load_exe;
end else begin
dsp21k_load_exe := GetProcAddress(HLib, ‘dsp21k_dl_exe’);
dsp21k_dl_exe := dsp21k_load_exe;
end;
dsp21k_start := GetProcAddress(HLib, ‘dsp21k_start’);
dsp21k_reset_bd := GetProcAddress(HLib, ‘dsp21k_reset_bd’);
dsp21k_ul_32s := GetProcAddress(HLib, ‘dsp21k_ul_32s’);
dsp21k_dl_int := GetProcAddress(HLib, ‘dsp21k_dl_int’);
dsp21k_ul_int := GetProcAddress(HLib, ‘dsp21k_ul_int’);
dsp21k_proc_running := GetProcAddress(HLib, ‘dsp21k_proc_running’);
if @dsp21k_open_by_id = nil then exit; // обрабатываем исключения–
if @dsp21k_close = nil then exit;
if @dsp21k_dl_exe = nil then exit;
if @dsp21k_start = nil then exit;
if @dsp21k_reset_bd = nil then exit;
if @dsp21k_dl_32s = nil then exit;
if @dsp21k_dl_int = nil then exit;
if @dsp21k_loaded_file = nil then exit;
if @dsp21k_ul_int = nil then exit;
result:= true
end;
После чего, заведем массивы для хранения отсчетов комплексных и квадратурных составляющих сигнала dim[], qcos[], qsin[] и расчетных значений спектра bpf[]. И инициализируем процессорную плату (см. листинг 2):
ЛИСТИНГ–2
инициализируем процессорную плату
…
Dim : array [0..10000] of real;
qcos : array [0..10000] of extended;
qsin : array [0..10000] of extended;
bpf : array [0..20000] of extended;
procedure TForm1.init_adc;
var i,j: integer;
p : pointer;
begin
adr_en_data := $50000; // адрес флага разрешения передачи для нижнего уровня
adr_val_data := $52713; // адрес области памяти с данными
cnt_data := 1000; // кол–во отсчетов
id_mod := 0; // 0 – слот для платы сбора данных
id_proc := 1; // 1 – процессор на плате
try
SetLength(ArrData, cnt_data); // задаем массив для отсчетов
// ===============================================================================
// так как мы заранее не знаем, в каком слоте и в каком процессоре загружена
// прошивка, то “пробегаемся” по всем слотам и процессорам и считываем
// возвращаемый указатель (хэндл)
// в двумерный массив–матрицу indata[][], после чего осуществляем сброс, загрузку
// прошивки и старт процессора на плате Hammerhead
// ===============================================================================
for i:=0 to 63 do //
for j:=1 to 4 do begin
p:= dsp21k_open_by_id(i, j);
if p<>nil then indata.PHandle:= p // просто двумерный
end;
dsp21k_reset_bd(indata[id_mod, id_proc].PHandle); // сброс прошивки
dsp21k_load_exe(indata[id_mod, id_proc].PHandle, ‘data\21160.dxe’);// загрузка
// ELF файла
dsp21k_start(indata[id_mod, id_proc].PHandle); // старт прошивки
sleep(10) // делаем на всяк пожарный задержку–
except show_tn(2, ‘Ошибка доступа. Нет модуля или связи…’,’SPEKTRA’); gl_adc:= false end
end;
Получение квадратур сигнала позволяет судить о фазовых неравномерностях в каналах и позволяет производить более тонкую подстройку модулей АЦП. Теория их формирования выходит за рамки данного материала и вряд–ли заинтересует читателя, поэтому приведем лишь сам код (см. листинг 3):
ЛИСТИНГ–3
вычисление квадратур сигнала
…
procedure quadr(auto:boolean;m:integer;var qcos,qsin:array of extended;var dim:array of real;var quad:integer);
var k1, k2, k3, k4, k5, k6, k7, k8, kcos, ksin,
a, aa, b, bb, c, cc, d, dd, f, vcos, vsin: real;
i: integer;
begin
k1:= 0.1169777; // коэффициенты окна–
k2:= 0.4131758;
k3:= 0.7499999;
k4:= 0.9698463;
k5:= 0.9698463;
k6:= 0.7499999;
k7:= 0.4131758;
k8:= 0.1169777;
kcos:= 4*(k1 – k3 + k5 – k7 – k2 + k4 – k6 + k8);
ksin := 4*(k1 – k3 + k5 – k7 + k2 – k4 + k6 – k8);
randomize;
if auto then // формирование псевдопоследовательности
for i:=0 to m do dim:= trunc((512 * sin((sink * i * pi/2) + pi/4) + random(4)) / 4);
m:= ceil(m/32); // отсчет = 32 бита
quad:= m; // кол–во отсчетов
for i:=1 to m do begin
a:= (dim + dim + dim + dim)*k1;
b:= (dim + dim + dim + dim)*k2;
c:= (dim + dim + dim + dim)*k3;
d:= (dim + dim + dim + dim)*k4;
aa:= (dim + dim + dim + dim)*k1;
bb:= (dim + dim + dim + dim)*k5;
cc:= (dim + dim + dim + dim)*k6;
dd:= (dim + dim + dim + dim)*k7;
f := a – c + aa – cc;
vcos := f – b + d – bb + dd;
vsin := f + b – d + bb – dd;
qcos:= vcos + kcos * 10; // Re – действительная часть
qsin := vsin + ksin * 10; // Im – мнимая часть
end
end;
И собственно то, ради чего все задумывалось, выборка данных с нижнего уровня, передача отсчетов в уже знакомую нам процедуру БПФ, поиск максимума и определение среднего уровня шума в полученном спектре (см. листинг 4):
ЛИСТИНГ–4
передача флага разрешения на нижний уровень и синхронное чтение блока данных
…
procedure TForm1.load_adc;
var a : array [1..20000] of double;
b : array [1..20000] of double;
st: array [0..10000] of real;
exp, mant, lab: string;
i, nf, nn, ns, n: integer;
signoise, re, im, l, m, druc, dmax: extended;
begin
series2.Clear; series3.Clear; inwav1.Clear; // очищаем серии при каждой выборке
dsp21k_dl_int(indata[id_mod, id_proc].PHandle, adr_en_data, 1); //set bit enable
// read data
while dsp21k_ul_int(indata[id_mod, id_proc].PHandle, adr_en_data)<>0 do ;
// ожидаем готовности платы
// передаем на нижний уровень указатель на массив ArrData для заполнения–
dsp21k_ul_32s(indata[id_mod, id_proc].PHandle, adr_val_data, cnt_data,
@ArrData[0]);
for i:= 0 to length(ArrData) – 1 do begin
inwav1.add(ArrData);
dim:= ArrData; // заносим значения отсчетов в массив dim[] для
// расчета квадратур
end;
if not gl_viborka then begin // выбор режима визуализации – отсчеты / спектр
series2.Assign(inwav1);
series2.SeriesColor:= clred // fix – цвет серии пропадает
end else begin
// ––––––––––––––––––––––––––––
quadr(false, 999, qcos, qsin, dim, quad);
signoise:= –1000; // fix – задаем минимальный уровень шума
nf:= 999;
ch.Title.Text.Text:= ‘Спектр сигнала на выходе приемного модуля при ‘ +
inttostr(nf–1) + ‘–точечном БПФ’;
nn:= ceil(20000000/nf);
for i:=0 to nf do begin
a:= dim;
b:= 0
end;
re:=0; im:=0;
// ==============================================================================
// получение спектра
// ==============================================================================
fft(a, b, nf, 4, 1);
for i:=1 to nf–1 do begin
st:=sqrt(a*a+b*b); // получаем модуль
if st=0 then st:= 1e–100;
bpf:= 20 * log10(st / nf) // переводим в дБ
end;
m:=0;
for i:=0 to ceil(nf/2–1) do begin
mant:= inttostr(trunc(i*nn/1000000));
ns := trunc((frac(i*nn/1000000))*10000);
exp := inttostr(ns);
if ns<10 then lab:= mant +’ ,’ + ‘000′ + exp;
if (ns<100)and(ns>9) then lab:=mant + ‘,’ + ‘00′ + exp;
if (ns<1000)and(ns>99) then lab:=mant + ‘,’ + ‘0′ + exp;
if ns>1000 then lab:=mant + ‘,’ + exp;
series2.addy(bpf, lab, clblue);
if (series2.YValue > signoise) and (series2.YValue <= 0) then begin
signoise:= series2.YValue;
n:= i
end
end;
// ==============================================================================
// для нормирования по уровню необходимо определить максимум, для этого сканируем
// отсчеты и определяем уровень больший заданного signoise = –1000
// ==============================================================================
signoise:=–1000;
for i:= 10 to nf div 2–5 do begin
if bpf > signoise then begin
signoise:= bpf;
k:= i
end
end;
// ==============================================================================
// поиск среднего шума
// ==============================================================================
nn:= 0;
re := 0;
druc:= –1000;
for i:= 10 to nf div 2–5 do begin
if i < k–10 then begin
re:= re + bpf * bpf;
inc(nn);
if druc < bpf then druc:= bpf
end;
if i > k+10 then begin
re:= re + bpf * bpf;
inc(nn);
if druc < bpf then druc:= bpf
end
end;
re:= –sqrt(re/nn);
// ==============================================================================
// отрисовываем
// ==============================================================================
for i:=0 to ceil(nf/2–1) do series3.Addy(re, ”, clred);
end;
Скомпилировав проект, запустим его на выполнение. Подключив генератор сигналов на вход приемного модуля, подадим тестовый синусоидальный сигнал. В результате уже можем наблюдать отсчеты и при необходимости сами квадратуры (чередующиеся мнимые и действительные составляющие) сигнала (см. рис.5 и 6):
Работа с пультом ДУ
Несмотря на то, что данная «фича» не является основной функцией в такого рода программах, а в некоторых случаях и вредной , но обойти ее стороной никак не могу. Что для этого нужно? Да всего ничего, либо собрать приемник на COM порт и управлять через WinLirc, либо использовать USB.IR приемник, что даст гораздо более стабильные результаты. Было реализовано оба варианта. Чтобы не увеличивать код, сигнатуры нескольких кнопок с ИК пульта были сняты заранее и введены в виде констант. Сама конструкция USB.IR приемника, программа и алгоритм декодирования пакетов были подробно рассмотрены в статье [3], поэтому тут заострять внимание на них не будем и перейдем сразу к коду (см. листинг 5):
ЛИСТИНГ–5
управление режимами с пульта через приемник USB
…
procedure tform1.ic(i: integer);
begin
case i of // нажатия на кнопки
1: image1.OnClick(nil); // чтение с текущего устройства–
2: image2.OnClick(nil); // загрузить файл WAV/MP3 или потоковые данные (дамп)–
3: image3.OnClick(nil); // переключение режимов ”спектр / отсчеты”–
4: image4.OnClick(nil); // вывод на печать–
5: image5.OnClick(nil); // информация о программе–
6: image6.OnClick(nil) // закрыть программу–
end
end;
// ===============================================================================
// сигнатуры кнопок 1, 2, 3, 4, 5, 6 пульта SR–003
// ===============================================================================
var remote: array[0..5] of string = (
‘12–08–13–07–07–07–13–08–12–08–06–07–06–07–06–08–05–08–06–07–14–07–05–09–05–07–14–06–14–07–12′,
‘13–07–14–07–13–08–05–08–13–07–06–08–13–07–06–07–13–08–06–07–06–07–14–07–06–07–13–08–06–07–12′,
‘13–08–13–07–13–08–13–07–13–07–07–07–06–07–06–08–05–08–06–07–06–07–06–08–06–07–13–08–12–08–11′,
‘13–08–12–08–13–07–13–08–13–08–06–07–13–08–05–08–06–07–06–07–06–07–06–08–05–08–13–07–06–08–14′,
‘13–08–12–07–06–07–06–07–14–08–05–08–05–08–05–08–13–07–06–08–13–07–13–08–06–08–12–07–14–07–12′,
‘14–07–13–07–06–07–06–08–06–07–13–08–13–07–06–08–05–08–06–07–14–07–13–07–13–08–06–07–06–07–12′);
procedure TForm1.tvd3uTimer(Sender: TObject);
var i: integer;
s: string;
j,k,z: smallint;
p: boolean;
begin
if (n5.Checked)and(gl_adc) then load_adc; // визуализация спектра в динамике–
// ===============================================================================
// получение кода кнопки с USB приемника (сигнатуры)
// ===============================================================================
if (GetInfraCode(’ra_usb’) = 1) then Exit;
if DataLength = 0 then Exit;
for i:= 0 to DataLength–1 do begin
s:= s + inttohex(InputInfraData, 2);
if i<> DataLength–1 then s:= s + ‘–’
end;
// ===============================================================================
// проверка сигнатуры с учетом интервала доверия и передача на
// интерпретатор команд
// ===============================================================================
for i:=1 to 6 do
for z:= 0 to DataLength–1 do
if (strtoint(’$’+copy(s,(z*3)+1,2))–dover >=
strtoint(’$’+copy(remote,(z*3)+1,2)))or
(strtoint(’$’+copy(remote,(z*3)+1,2)) <=
strtoint(’$’+copy(s,(z*3)+1,2))+dover) then begin
p:= true;
k:= z
end else begin p:= false; break end;
if p then ic(k)
end;
Осталось проверить работоспособность управления по ИК. Для этого, подключив USB.IR приемник, понажимаем кнопку «2» на пульте, отвечающую за смену режима отображения «спектр / отсчеты» (см. рис.7):
Заключение
Рассмотренный виртуальный прибор позволяет сэкономить время и снизить стоимость любой системы сбора и анализа данных с модулей АЦП без привлечения дорогой специализированной аппаратуры, за счет применения гибкого ПО в сочетании с производительностью DSP и простоты «Plug&Play» подключения (cPCI) промышленных плат фирмы Bittware.
Полные исходные тексты и компиляцию виртуального спектроанализатора SPEKTRA (файл fft2.zip) вы можете загрузить на форуме клуба программистов (раздел «Журнал клуба программистов. Первый выпуск») или с сайта автора [5]. Если тема представляет для вас интерес – пишите, задавайте вопросы на форуме http://www.programmersforum.ru
Ресурсы
- С.Бадло. Быстрое преобразование Фурье. Практика использования. – Блог клуба программистов,
05.02.2010 http://pblog.ru/?p=658 и http://www.programmersforum.ru/showthread.php?t=83467
- Data Sheet Kontron Modular Computers GmbH, 2003, ID26799, rev.01
- Practical Design Techniques for Sensor Signal Conditioning, Analog Devices, 1998
- E.Бадло, С.Бадло. USB термометр и дистанционка в одном флаконе. Часть 2. – Радиолюбитель, 2010,
№1, с.48 http://raxp.radioliga.com/cnt/s.php?p=us2.pdf или с форума клуба программистов
http://www.programmersforum.ru/attachment.php?attachmentid=17684&d=1258320468
- Ресурсы и компиляция проекта SPEKTRA http://raxp.radioliga.com/cnt/s.php?p=fft2.zip
Статья из первого выпуска “журнала ПРОграммистов”.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Быстрое преобразование Фурье. Практика использования. Часть 2
25th
Применение изометрических координат в Delphi
В данной статье рассмотрены методы применения изометрии на канве. Позволяет получить псевдо-эффект 3D на двухмерной плоскости.
Автор: Владимир Дегтярь aka DeKot degvv@mail.ru
Рис. 1 Игровое поле в прямоугольных и изометрических координатах.
1. Построение изометрической матрицы
При создании простых 2D игр (аркады, «стрелялки» и т.п.) обычно для построения игрового поля используется двухмерная матрица с координатами, привязанными к координатам экрана (формы). При этом, направления координат игрового поля и экрана совпадают, и плоскость поля располагается как бы вертикально перед пользователем. Некоторую объемность изображения и эффект перспективы, правда, можно получить за счет манипулирования масштабом графических элементов. Но все это достигается путем значительного усложнения кода программы и требует сложных математических вычислений.
Значительно лучший визуальный эффект можно получить при применении изометрических координат для игрового поля. В этом случае поле для пользователя как бы наклонено под определенными углами по отношению к плоскости, а значит и координатам, экрана. При применении матрицы в изометрических координатах требуется привести координаты ячеек поля и индексы массивов, описывающих такую матрицу к прямоугольным координатам экрана. Для случая, когда матрица игрового поля выполнена в изометрических координатах, можно применить следующие функции для определения индексов массива по координатам формы или же координаты ячеек массива по индексам. Необходимые данные приведены на рисунке 2:
Рис. 2 Необходимые данные.
Думаю понятно, что (см. листинг 1):
ЛИСТИНГ-1
…
mas: array [ 0..m,0..n ] of < тип данных >,
где i — в диапазоне 0 .. m, j — в диапазоне 0..n;
Left, Top – смещение от края формы;
dxc, dyc – шаг координат ячеек матрицы;
dxc := 2 * dyc;
wp, hp – ширина и высота ячейки матрицы в координатах формы;
wp := 2 * dxc; hp := 2 * dyc;
x0 = Left; y0 = Top;
Для определения координат ячейки по индексам (см. листинг 2):
ЛИСТИНГ-2
…
function Kord_X(i , j : byte) : integer;
begin
Result:= ((m + ( j – i)) * dxc ) + Left
end ;
x := Kord_X(i , j);
function Kord_Y(i , j : byte) : integer;
begin
Result := ((i + j) * dyc) + Top
end;
y := Kord_Y(i , j);
Для определения индексов по координатам (см. листинг 3):
ЛИСТИНГ-3
…
function Ind_I (x , y : integer) : integer;
begin
Result := ((m – ((x – Left) div dxc)) + ((y -Top) div dyc)) div 2
end;
i := Ind_I (x , y);
function Ind_J (x , y : integer) : integer;
begin
Result := (((x -Left) div dxc) + (((y -Top) div dyc) – m)) div 2
end;
j := Ind_J (x , y);
При определении координат по курсору мыши , необходимо назначить область “чувствительности” курсора в пределах области , показанной на рисунке оранжевым цветом. Тогда координаты x , y ячейки матрицы по координатам курсора xm , ym определяются следующим образом (см. листинг 4):
ЛИСТИНГ-4
…
procedure TForm1.FormMouseUp (Sender : TObject ; Button : TMouseButton ;
Shift : TShiftState ; xm , ym : Integer) ;
begin
x := ((((xm – Left) + (dxc div 2)) div dxc) * dxc) + Left;
y := ((((ym – Top) + (dyc div 2)) div dyc) * dyc) + Top
end;
2. Графические объекты в изометрических координатах
Для удобства работы с графикой в изометрических координатах следует тщательно подходить к соотношениям размеров объектов и размерами ячеек матрицы. Так, углы расположения матрицы – 27? и 63? указаны не случайно. При работе с пиксельными изображениями форматов BMP, JPG, PNG и аналогичных этот наклон наиболее удобен для отображения различных элементов.
Для движущихся объектов, реализуемых в виде отдельных рисунков или спрайтов следует применять следующие пропорции в размерах: Sprite.Width:= 1 / 3 * wp; Sprite.Height:= 2 / 3 * hp или Sprite.Width:= 2 / 3 * wp; Sprite.Height:= hp.
Здесь: wp и hp — соответственно ширина и высота ячейки матрицы (см. рис.3).
Рис.3 Организация движения спрайта.
При организации движения спрайта приращения по координатам dx и dy должны иметь соотношение 2:1 и соответствовать условию:
N_step = dxc / dx, или N_step = dyc / dy;
Где N_step — количество приращений за один такт (шаг) в цикле или по таймеру ;
dxc , dyc — шаг координат ячеек изометрической матрицы ;
Приведем пример (см. листинг 5):
ЛИСТИНГ-5
…
for i:= 1 to N_step do begin
Sprite(x,y) ; // процедура вывода спрайта на форму в координатах x , y;
x := x + dx ; y := y + dy ;
end;
При выполнении такого условия координаты спрайта всегда после выполнения шага движения попадают в координаты следующей ячейки. При использовании обработчика нажатия клавиш «cтрелки» приращения координат спрайта принимают следующие значения (см. рис.4):
Рис. 4. Приращение координат спрайта
3. Многомерная матрица игрового поля в изометрических координатах.
До сих пор мы рассматривали двухмерную изометрическую матрицу, расположенную в одной плоскости. Для получения реального трехмерного изображения можно применять многомерную матрицу в трех изометрических координатах (см. рис.5):
Рис. 5. Отображение многомерной матрицы в изометрических координатах
Такая матрица описывается следующим массивом (см. листинг 6):
ЛИСТИНГ-6
…
mas_index : array [ 0 .. l , 0 .. m , 0 .. n ] of < тип данных > или…
mas_index [ k , I , j ] ; здесь индекс k находится в диапазоне значений 0..l;
i — 0..m;
j — 0..n;
Работа с такой матрицей в пределах одного слоя аналогична описанию в разделе 1 в соответствии с рисунком 1. Однако при переходе с одного уровня на другой следует учитывать следующие особенности:
- каждый последующий слой в экранных прямоугольных координатах сдвигается на величину dyc
- при организации движения графических объектов приращения координат объекта в экранных
координатах задаются одинаковыми dx := dy и выполняется условие dx * N_step = dxc
В этом случае, при переходах между уровнями (при использовании обработчика клавиш «стрелки») изменения индексов ячеек матрицы следующие (см. таблицу и рисунок 6):
Таблица. Изменения индексов ячеек матрицы
Рис. 6. Визуализация переходов
Далее, после перехода на следующий уровень обработка кода происходит как с двухмерной матрицей с учетом новых индексов в массиве индексов.
Заключение
Пример применения многомерной изометрической матрицы приведен в ресурсах к статье на http://www.programmersforum.ru в разделе «Журнал клуба программистов. Первый выпуск». В следующих уроках мы научимся работать с графикой на канве в среде Дельфи
Статья из первого выпуска “журнала ПРОграммистов”.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Применение изометрических координат в Delphi
25th
Простейшая программа WinAPI на C++
Многие, кто переходит с «учебного» ДОСовского компилятора вроде Borland C++ на визуальное программирование быстро запутываются в сложных библиотеках типа MFC или VCL, особенно из-за того, что новые создаваемые проекты уже содержат с десяток файлов и сложную структуру классов. Рано или поздно встает вопрос: «…а почему нельзя написать оконную программу с простой линейной структурой, состоящую из одного файла .cpp?» На самом деле можно. Для этого нужно работать с низкоуровневыми функциями операционной системы – API.
Простейшая программа WinAPI на C++
Дмитрий Федорков
dsDante www.programmersforum.ru
Windows API (application programming interfaces) – общее наименование целого набора базовых функций интерфейсов, программирования приложений операционных систем семейств Windows и Windows NT корпорации «Майкрософт». Является самым прямым способом взаимодействия приложений с Windows.
Википедия
Зачем нам вообще API
Все что делает любая программа – делает либо непосредственно с помощью инструкций процессора, либо обращаясь к функциям биоса (хотя их прямые вызовы используются всё реже), либо через системные функции (API). К последним относится: прорисовка окон, получение координат мыши, чтение файлов и т. д.
WinAPI – это основа, в который должен разбираться любой программист, пишущий под Windows, независимо от того, использует ли он библиотеки вроде MFC (Microsoft Visual C++) или VCL (Borland Delphi / C++ Builder). Часто бывает проще написать простенькую программу, состоящую из одного файла, чем настраивать относительно сложный проект, созданный визардами. Я не говорю уже, что программа получается гораздо оптимизированнее (всё-таки низкоуровневое программирование) и в несколько раз меньше. К тому же у них не возникает проблем совместимости, если у конечного пользователя не хватает каких-т
о библиотек, чем иногда грешит MFC.
Наша программа
Напишем простую программу: окно, в нем – синусоида, которая движется влево, как график функции
y = sin (x + t). Если кликнуть мышкой по окну, анимация приостановится, или наоборот продолжится. Чтобы было проще разобраться, я сразу приведу весь исходный код, а потом прокомментирую ключевые места. Попробуйте самостоятельно модифицировать разные части программы, пробуйте оптимизировать мою программу, может вам даже удастся найти ошибки в коде (см. листинг):
ЛИСТИНГ
тестовая программа
…
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <cmath>
LRESULT CALLBACK WindowProc (HWND, UINT, WPARAM, LPARAM);
HDC dc;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// Create window
WNDCLASS wc = {0};
wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.lpszClassName= L”CMyWnd”;
RegisterClass (&wc);
HWND hWnd = CreateWindow (L”CMyWnd”, L”WinMain sample”, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 320, 240, NULL, NULL, hInstance, NULL);
dc = GetDC (hWnd);
ShowWindow (hWnd, nCmdShow);
// Message loop (timer, etc)
SetTimer (hWnd, 1, USER_TIMER_MINIMUM, NULL);
MSG msg;
while (GetMessage(&msg,NULL,0,0) > 0)// while not WM_QUIT (0) nor some error (-1)
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
return msg.wParam;
}
// Message processing function
LRESULT CALLBACK WindowProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static bool Move = true;
static int Phase=0, Width, Height;
switch (message)
{
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
Move = !Move;
// no break
case WM_TIMER:
if (Move)
Phase++;
// no break
else
break;
case WM_PAINT:
Rectangle (dc, -1, -1, Width+1, Height+1);
MoveToEx (dc, 0, Height * (0.5 + 0.3*sin(0.1*Phase)), NULL);
for (int i=0; i<Width; i++)
LineTo (dc, i, Height * (0.5 + 0.3*sin(0.1*(i+Phase))) );
break;
case WM_SIZE:
Width = LOWORD(lParam),
Height = HIWORD(lParam);
break;
case WM_KEYDOWN:
if (wParam != VK_ESCAPE)
break;
// else no break
case WM_DESTROY:
PostQuitMessage (0);
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
Обращаю ваше внимание на то, что эта программа писалась под Visual C++. У Билдера может быть проблема из-за заголовка <cmath>, вместо него нужен <math.h>. Для этой программы понадобится пустой проект с единственным файлом .cpp. В Visual Studio в свойствах создаваемого проекта нужно отметить галочку «Empty project». Итак, приступим…
Пройдемся по коду
В программе мы добавляем заголовочный файл <cmath>, который нужен для расчета синусоиды, и <windows.h>, который содержит все функции WinAPI. Строчка #define WIN32_LEAN_AND_MEAN отключает некоторые редко используемые функции и ускоряет компиляцию.
Функцию WindowProc() пока пропустим, оставив на сладкое.
HDC – контекст устройства рисования. Не будем подробно останавливаться на графике – это не основная тема статьи, да и используется не очень часто. Скажу лишь, что эта переменная глобальная, потому что используется в обеих функциях. Надо добавить, что буква ”H” в типе данных WinAPI (в “HDC”) обычно означает ”Handle” (хэндл), т.е. переменную, дающую доступ к самым разным устройствам, объектам и прочим сущностям WinAPI. Хэндл – представляет собой обычный указатель, работа с которым зависит от контекста (от типа переменной). Вообще, хэндлы – сложная тема, без к
оторой тоже поначалу вполне можно обойтись.
Теперь главное (main) – точка входа. В консольных программах функция main может возвращать либо void, либо int, а также может иметь или не иметь аргументы (int argc, char **argv). Итого 4 варианта. В нашем случае используется функция WinMain(), которая может иметь только такой вид, как в примере. Слово WINAPI (которое подменяется препроцессором на __stdcall) означает, что аргументы функции передаются через стек*. Аргумент HINSTANCE hInstance — хэндл текущего процесса, который бывает нужен в некоторых ситуациях. Назначение следующего аргумента HINSTANCE hPrevInstance весьма смутное, известно только, что э
та переменная всегда равна NULL. В исходниках квейка можно даже найти такую строчку: if (hPrevInstance != NULL) return 0.
* подробнее – в учебниках по ассемблеру
Аргумент LPSTR lpCmdLine – командная строка. В отличие от консольного main (int argc, char **argv), эта строка не разделена на отдельные аргументы и включает имя самой программы (что-нибудь типа “C:\WINDOWS\system32\format.com C: \u”). Далее int nCmdShow определяет параметры окна, указанные например, в свойствах ярлыка (это будет нужно при создании окна).
Перейдем, наконец, к выполняемому коду. В первую очередь нам нужно создать окно. Структура WNDCLASS хранит свойства окна, например текст заголовка и значок. 4-ре из 9-ти полей структуры должны быть нулевые, поэтому сразу инициализируем ее нулями. Далее CS_HREDRAW | CS_VREDRAW означает, что окно будет перерисовываться при изменении размера окна. wc.hInstance задаёт текущий процесс (тут-то и понадобился этот аргумент из WinMain). Еще также нужно явно указать мышиный курсор, иначе, если это поле оставить нулевым, курсор не будет меняться, скажем, при переходе с границы окна на са
мо окно (попробуйте сами). wc.lpfnWndProc – это адрес функции, которая будет обрабатывать все события. Такие как нажатие клавиши, движение мыши, перетаскивание окна и т. д. После знака ”=” просто указываем имя нашей функции. Позже мы напишем эту функцию, которая и будет определять реакцию программы на интересующие нас события.
WNDCLASS – это не само окно, а класс (форма), экземпляр которого и будет нашим окном. Но перед созданием окна нужно зарегистрировать в системе этот класс. Задаем его имя в системе CMyWnd и регистрируем класс.
Функция создания окна CreateWindow() достаточно простая и вместо того, чтобы перечислять все ее аргументы, опять сошлюсь на интернет. Кому мало одиннадцати аргументов, могут попробовать функцию CreateWindowEx(). Обратите внимание – все строковые аргументы предваряются буквой L, что означает, что они – юникодовые. Для многих функций WinAPI существует по два варианта: обычный ANSI и юникодовый. Соответственно они имеют суффикс A или W, например CreateWindowA и CreateWindowW. Если вы посмотрите определение функции в <windows.h>, то увидите что-то типа #define CreateWindow
CreateWindowW. Вместо CreateWindow() мы можем явно вызывать CreateWindowA() с обычными строками (без приставки L).
Описание GetDC() и ShowWindow() снова пропущу (кому интересно – тот легко найдет).
Дальше начинается самое интересное – работа с событиями. Для начала создадим таймер, который будет генерировать соответствующее событие 65 раз в секунду (фактически максимальная частота, по крайней мере для Windows XP). Если вместо последнего аргумента SetTimer() написать имя подходящей функции, она будет вызываться вместо генерации события.
Далее идет то, что называется message loop – цикл обработки событий. Мы принимаем событие и обрабатываем его. В нашем случае можно убрать TranslateMessage(&msg), но эта функция понадобится, если на основе этого примера кто-нибудь будет создавать более сложную программу (с обработкой скан-кодов клавиатуры). Если мы получаем событие выхода программы, то GetMessage() возвращает ноль. В случае ошибки возвращается отрицательное значение. В обоих случаях выходим из цикла и возвращаем код выхода программы.
Теперь займемся функцией обработки событий WindowProc(), которую мы оставили на сладкое. Эта функция вызывается при любом событии. Какое именно сейчас у нас событие – определяется аргументом message. Дополнительные параметры (например, координаты мыши в событии “мышь двинулась”) находятся в аргументах wParam и lParam. В зависимости от того, чему равно message, мы совершаем те или иные (или вообще никакие) действия, а потом в любом случае вызываем DefWindowProc, чтобы не блокировать естественные реакции окна на ра
зные события.
Вообще то, что я сделал с оператором switch больше похоже на стиль ассемблера и порицается большинством серьезных разработчиков. Я имею в виду сквозные переходы между case- ми (там, где нет break). Но пример простой, к тому же у меня было настроение “похакерить”, так что не буду лишать вас удовольствия разобраться в этом коде.
Имена констант message говорят сами за себя и уже знакомы тем, кто работал с MFC. При событии WM_PAINT рисуем белый прямоугольник, а на нём — чёрную синусоиду. На каждый WM_TIMER смещаем фазу синусоиды и перерисовываем ее. На клик мыши запускаем или останавливаем движение, при этом, если нажать обе кнопки одновременно, то фаза увеличится ровно на 1, для чего здесь и убран break (см. рисунок). При изменении размера окна мы обновляем переменные Width и Height за счёт того, что в lParam хранятся новые размеры. Всегда нужно вручную обрабатывать собы
25th
Установка отступов для логических блоков программы
Данная статья раскрывает некоторые тонкости алгоритма одного из элементов форматирования исходных текстов программ. Рассчитана на широкий круг программистов и не требует специальных знаний.
Автор: Utkin www.programmersforum.ru
Краткий экскурс…
Прежде всего, как советуют классики, нужно определиться со структурами данных, в которых будет проводиться основная работа по форматированию. Большинство языков программирования имеют поддержку массивов различных типов данных. Поэтому будем представлять входящий текст программы как массив строк. Теперь рассмотрим вопрос о представлении отступа. В зависимости от реализации это может быть задание отступа отрисовки символов в компоненте вывода текста на экран, либо задание символа табуляции (в случае если транслятор позволяет использовать табуляцию в программах), либо просто определенное количество символов пробела в качестве заменителя символа табуляции. Теперь о самих отступах – все, что нам требуется, это либо добавление в начало строки определенного символа (или группы символов, в случае если символ табуляции будет заменен на пробелы), либо передача информации компоненту отрисовки текста программы о величине отступа от левого края для конкретной строки. Важное условие – величина отступа меняется в зависимости от уровня вложенности логических блоков программы.
Еще немного конкретики – нам нужно знать, как в данной программе представляются операторные скобки. Дадим данному понятию свое определение – маркер начала логического блока и маркер конца логического блока. Для нашего алгоритма это просто строка–образец, содержащая в себе операторную скобку.
Также нам нужно где-то помнить текущую величину отступа, в чем она будет выражаться – в пикселях отступа для компонента, число символов табуляции или число символов пробела, зависит только от конкретных реализаций данного алгоритма. Теперь введем также определение шага отступа – то есть, на сколько смещается отступ от левого края в зависимости от уровня вложенности логических блоков программы. Так, в таблице величина отступа для содержимого логического блока будет равна сумме общего отступа и отступа текущего блока, а шаг отступа будет равен отступу текущего блока:
Таблица. Условное представление логических блоков:
В общем, имеющейся информации уже достаточно для построения алгоритма установки отступов для логических блоков программы.
Итак, приступим
Сначала обычно имеет место быть инициализация, то есть создание необходимых структур и установка конкретных значений для величины отступа, шага отступа, маркеров начала и конца логических блоков. Весь алгоритм будет представлять собой один цикл по всем элементам массива строк – хранилище нашего кода программы:
1. Получим очередную строку программы из массива строк.
2. Удалим из левой ее части все пробелы и символы табуляции (если они там имеются).
3. Получим первую лексему языка программирования из нашей строки. Это может быть символ или ключевое слово.
4. Сверим наше слово с маркерами
5. Если слово является маркером начала логического блока, то
5.1. В зависимости от особенностей реализации – добавим в начало строки символы табуляции (или символы пробела) либо передадим информацию в компонент по отрисовке строк программы в соответствии с текущим отступом.
5.2. Увеличим отступ на величину шага отступа.
5.3. Закончим текущую итерацию цикла
6. Если слово является маркером конца логического блока, то
6.1. Если разница между отступом и шагом отступа будет больше или равно нулю, то уменьшим отступ на величину шага отступа.
7. В зависимости от особенностей реализации – добавим в начало строки символы табуляции (или символы пробела) либо передадим информацию в компонент по отрисовке строк программы в соответствии с текущим отступом.
Некоторые комментарии по пунктам алгоритма*
Второй пункт алгоритма нацелен на удаление существовавшего до этого форматирования. Программист или какая–либо другая программа могли использовать другие принципы форматирования кода, и если бы данного пункта не было, то возникла–бы путаница: отступы наложились друг на друга и результат отличался от ожидаемого.
Реализация третьего пункта зависит от конкретного языка программирования программу, на котором предстоит форматировать. Для многих языков программирования лексему можно определить как последовательность символов (или одиночный символ) отделенный от другой лексемы разделителем, в качестве которого могут выступать пробелы, символы табуляции и другие лексемы – спецсимволы, скобки т.д.
Пункты 5 и 6 могут быть изменены в зависимости от языка программирования. Это связано с тем, что некоторые из языков воспринимают регистр введенных лексем, а некоторые его игнорируют. Если программа, которую нужно форматировать составлена на языке программирования не чувствительного к регистру, то перед проверкой первой лексемы на равенство маркерам начала и конца логического блока их необходимо преобразовать к одному регистру.
Пункт 5.3 предназначен для того, чтобы сразу не сдвигался маркер начала логического блока.
Пункт 6.1 нужен для того, чтобы отступ не получил отрицательного значения (не стал выступом). По сути, программа рассматривается как некий поток команд для стека. Итак, перед нами стек – это текущая величина отступа. Каждый маркер начала логического блока увеличивает его на величину шага отступа, а каждый маркер конца логического блока уменьшает его на ту же величину. Т.е. пункт 6.1. ответственен за защиту от опустошения стека: нельзя брать из стека больше, чем там имеется. В тоже время как в самой программе такие ошибки вполне возможны – к примеру, программист при наборе программы мог забыть поставить одну (или несколько) из открывающих операторных скобок.
* такой способ весьма прост, но имеет один недостаток – его нельзя использовать для языков
программирования, в которых отступы влияют на процесс вычисления программы – это Питон,
некоторые версии Хаскела и т.д.
Заключение
Данный алгоритм является только примером способов обработки исходных текстов программ. Как правило, в настоящих средах разработки подобные алгоритмы входят в состав более сложного алгоритма включающего в себя также функции по разделению строки на синтаксические элементы, автозамены, приведение к заданному регистру, удалению лишних пробелов и т.д. В тоже время он дает начальное представление о работе подобных систем в редакторах текста (не обязательно редакторах исходного текста программ).
Реализация рассмотренного алгоритма на Дельфи 7 прилагается в ресурсах к статье на http://www.programmersforum.ru в разделе «Журнал клуба программистов. Первый выпуск».
Статья из первого выпуска “журнала ПРОграммистов”.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Установка отступов для логических блоков программы
15th
Апр
Малобюджетный вариант глушилки GSM
Малобюджетный вариант глушилки GSM: можно собрать и в любительских условиях.
Для этого вам понадобятся две микросхемы ГУН TDK-2127/2128 из старых мобилок Siemens С25 и отечественная КМОП логика К561ЛА7 для ГКЧ. Для повышения выходной мощности можно добавить, снятые с этой-же мобилки, ВЧ усилители PF08105A. Питание – обычный 6В аккумулятор от брелка сигнализации.
Примечание.
Потребление в схеме достигает порядка полуампера, поэтому аккумулятор все-же стоит использовать
помощнее, например две последовательно соединенных АКБ от мобилок.
Статья из первого выпуска “журнала ПРОграммистов”.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
15th
Микросхемы динамической памяти DRAM
Микросхемы динамической памяти DRAM: (NEC 4164 и обычные КР565РУ5) можно использовать как видеокамеру. Сам принцип использования основан на эффекте ускорения разряда конденсаторов в ячейках памяти при воздействии на них света. Для этого нужно по всем адресам микросхемы со снятой крышкой
записать единицы, через некоторое время (без регенерации) – прочитать. Те ячейки матрицы памяти, на которые падает свет, разряжаются и переходят в состояние 0 раньше, которые остались в
тени – позже.
DRAM является микросхемой памяти, поэтому распределение логического адреса должно соответствует физическому месту на кристалле. Чтобы узнавать это распределение необходимо провести несколько испытаний. В микросхеме DRAM, адреса ячеек которой состоят из двух составляющих – адреса строк и адреса столбцов, можно предположить, что и в физической структуре кристалла они расположены точно
также. Как обнаружилось, это фактически так и есть, т.е. логические адреса строк соответствуют физическим строкам, и логические адреса столбцов соответствуют физическим столбцам.
Логические адреса в пределах строки соответствуют разумеется не физическому месту ячейке памяти на интегральной схеме на монокристалле. В обработанном изображение видно это ошибочное распределение перестановленными строками и столбцами. Перестановка разрядов адреса позволяет этот дефект устранить, но, тем не менее, здесь все-таки нужен экспериментальный подход. Геометрия интегральных схем на
монокристалле различных изготовителей может быть другой.
Интегральная схема на монокристалле не обеспечивает высокую чувствительность, так что время облучения кристалла более длительное, чем у настоящей CCD-видеокамеры. Отмечено, что кристалл
микросхемы DRAM более восприимчив к красному спектру оптического диапазона, чем к голубому, возможно он имеет хорошую спектральную чувствительность в инфракрасном спектре излучения.
Примечание. Конечно, можно рекомендовать заниматься подобными экспериментами, если СОВСЕМ БОЛЬШЕ НЕЧЕМ заняться или для изучения физических принципов процесса. Если требуется реальная камера, то дешевле и по затратам времени приобрести дешевую CCD со схемой управления.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
15th
Разгон 3G. USB модема
Разгон 3G.USB модема: для повышения уровня сигнала и как следствие стабильности коннекта и скорости, можно воспользоваться простым, не требующий затрат, способом.
Подопытным выступил «Билайновский» модем ZTE MF-622. Нужен провод ~20см длины, желательно медный (в резиновой оплётке или без, неважно). Оборачиваем несколько витков вокруг модема, расстояние между витками 1см. Также нам понадобиться CD или DVD диск, любой ненужный, и проутюжив его, слегка согните как показано на фото.
Направляем наш импровизированный девайс в ту сторону, где находится вышка оператора и… вуа-ля.
(автор, наш форумчанин Vasek123)
Статья из первого выпуска “журнала ПРОграммистов”.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
29th
Мар
Первый выпуск журнала “ПРОграммист”
Здравствуйте, уважаемые форумчани, коллеги!
Сегодня у нас радостное событие- выход первого номера журнала “ПРОграммист”!
Как и было запланировано журнал был с нуля сделан за 1 месяц, даже немного меньше =)
В нашем журнале, состоящего из 47 страниц формата А4, Вы найдете много интересной, эксклюзивной(все статьи авторские), и, надеемся, полезной информации.
В первый номер вошли статьи касающиеся правил правильного написания программ, работе с графикой в Delphi, для новичков в программировании вошла статья про создание простейшей программы на
WinAPI, а также статья посвященная быстрым преобразованиям Фурье (БПФ).
Также на страницах нашего журнала Вы увидите много интересных фактов и еще много много всего
Хочу поблагодарить всех участников, работающих над нашим первым выпуском, а именно:
Редакторов:
Utkin, JTG, Сергей Бадло aka raxp
Дизайнеров/верстальщиков:
Егор Горохов aka Revival001, Indian, Сергей Бадло aka raxp
И, конечно же, авторов, которые написали замечательные статьи:
Utkin, Виктор Кон, Владимир Дегтярь,
Дмитрий Федорков, Руслан Аблязов,
Сергей Бадло
Особенно хочу поблагодарить Сергея, ибо без него журнала не было бы.
С уважением, член редакторского состава журнала Шульга Алексей aka Levsha100.
10th
Фев
Функция GetKeyboardState копирует состояние 256 виртуальных клавиш в заданный буфер.
Один из вариантов использования – отслеживание нажатия клавиш.
Облако меток
css реестр ассемблер timer SaveToFile ShellExecute программы массив советы word MySQL SQL ListView pos random компоненты дата LoadFromFile form база данных сеть html php RichEdit indy строки Win Api tstringlist Image мысли макросы Edit ListBox office C/C++ memo графика StringGrid canvas поиск файл Pascal форма Файлы интернет Microsoft Office Excel excel winapi журнал ПРОграммист DelphiКупить рекламу на сайте за 1000 руб
пишите сюда - alarforum@yandex.ru
Да и по любым другим вопросам пишите на почту
пеллетные котлы
Пеллетный котел Emtas
Наши форумы по программированию:
- Форум Web программирование (веб)
- Delphi форумы
- Форумы C (Си)
- Форум .NET Frameworks (точка нет фреймворки)
- Форум Java (джава)
- Форум низкоуровневое программирование
- Форум VBA (вба)
- Форум OpenGL
- Форум DirectX
- Форум CAD проектирование
- Форум по операционным системам
- Форум Software (Софт)
- Форум Hardware (Компьютерное железо)