Программная реализация утилиты кодирования и декодирования формата BASE 64
Разработка утилиты кодирования и декодирования формата Base 64 в программной среде Linux с использованием компилятора. Написание программы на языке С++. Кодирование символьной строки любого набора байт в последовательность печатных ASCII символов.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | курсовая работа |
Язык | русский |
Дата добавления | 10.09.2013 |
Размер файла | 1,4 M |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
Размещено на http://www.allbest.ru/
ФЕДЕРАЛЬНОЕ Государственное АВТОНОМНОЕ образовательное учреждение высшего профессионального образования
БЕЛГОРОДСКИЙ ГОСУДАРСТВЕННЫЙ НАЦИОНАЛЬНЫЙ ИССЛЕДОВАТЕЛЬСКИЙ УНИВЕРСИТЕТ
(НИУ "БелГУ")
ФАКУЛЬТЕТ КОМПЬЮТЕРНЫХ НАУК И ТЕЛЕКОММУНИКАЦИЙ
КАФЕДРА МАТЕМАТИЧЕСКОГО И ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
ИНФОРМАЦИОННЫХ СИСТЕМ
Курсовая работа
Программная реализация ультилиты кодирования и декодирования формата BASE 64
студента дневного отделения 2 курса группы 141103
Дихтяренко Александра Анатольевича
Научный руководитель:
доцент В.В. Румбешт
БЕЛГОРОД 2013
Содержание
- Введение
- 1. Теоретическая часть
- 1.1 Что такое кодирования/декодирование формата BASE64, его применение
- 1.2 Структура BASE64
- 2. Практическая часть
- 2.1 Алгоритм кодирования/декодирования
- 2.2 Реализация программы
- 2.3 Апробация приложения
- Заключение
- Список используемой литературы
- Приложения
Введение
Необходимо разработать утилиту кодирования/декодирования формата BASE64. Разработка ведется в программной среде Linux, используя компилятор gcc. Программа будет писаться на языке С++.
Для того что бы выполнить поставленную задачу необходимо составить план для изучения данной темы и ее реализации, а конкретно:
· Сбор и изучение материалов по теме кодирования/декодирования формата BASE64;
· Разобраться с алгоритмом кодирования/декодирования;
· Реализовать алгоритм, используя полученные знания;
· Сделать вывод из проведенной работы
1. Теоретическая часть
1.1 Что такое кодирования/декодирование формата BASE64, его применение
Дело в том, что изначально для передачи электронной почты в Интернет использовался только текст (RFC822). Затем, с развитием компьютерных технологий, потребовалась возможность передачи нетекстовой информации: аудио, видео, графических файлов, файлов приложений и т.д. Однако почтовые сервера понимают только текст. Именно поэтому появилась необходимость каким-то образом преобразовать двоичный файл в текстовый для этого и был придуман BASE64. Этот способ используется в спецификации MIME.
MIME-это стандарт описания заголовков e-mail сообщений. Используя этот стандарт, в одном письме можно отправить сразу несколько различных вложений. Например, можно положить в письмо архив, видео, картинки или другие файлы. И все это отправить получателю. Почтовая программа-получатель, понимающая MIME, достанет из сообщения все, что было переданно.
Из этого можно сделать вывод, что Base64 - это схема кодирования символьной строки любого набора байт в последовательность только печатных ASCII символов.
утилита кодирование декодирование программный
Рисунок.1.1 ASCII символы
1.2 Структура BASE64
Так как в BASE64 используется алфавит из 64 символов, то для кодировки нам хватит вместо стандартных 8 байт шесть. При этом изменении, мы можем кодировать любые цифры с помощью нашего алфавита.
Преобразование происходит по принципу:
Берутся три последовательных байта по восемь бит (всего 24 бита), и побитно делятся на четыре 6-ти битных байта (всего 24 бита). Замечу, что используется только 6 битов, а 2 остаются "не использованными"
Схематично такое преобразование можно представить:
Рисунок. 1.2 Преобразование в 6 битный вариант
Так кодируется любая информация, но существует еще один важный момент, дело в том, что не обязательно всегда будет 3 байта, именно по этому отсутствующие байты заменяют символом "=”
2. Практическая часть
2.1 Алгоритм кодирования/декодирования
Кодирование Для начала взять 8-ой (старший) бит исходного байта и поместить его в начало 6-ти битового байта. Затем на место 8-го бита исходного байта поместить 7-й бит, а в 6-ти битовом байте первый бит (младший) переместить на место 2-го бита. После такого перемещения освобождается первый (младший) бит 6-ти битного числа. В него и поместим старший (бывший седьмой) бит исходного байта. Затем еще раз пердвинем биты в обоих байтах, после чего повторяем процедуру
Пример:
Рисунок 2.1 Пример
Рассмотрим шаг 1.
Для проверки установки старшего бита исходного 8-ми битного байта наложим на него так называемую "маску". Т.е. применим к нему побитовую операцию AND с числом 128 (10000000).
Рисунок 2.2 Наложение "маски-1"
Как видно из приведенной схемы, проверить установку старшего бита совсем несложно. Если результатом операции получается число 128, значит бит установлен, а если в результате получаем 0, значит старший бит не установлен. Далее в зависимости от полученного результата применим к 6-ти битовому байту битовую операцию OR с числом 1, которая в любом случае применения устанавливает первый (младший) байт в 1.
Рисунок 2.3 Наложение "маски-2"
Здесь необходимо учитывать то, что перед применением побитовой операции OR с числом 1 младший бит 6-ти битового байта всегда 0. В случае для первых двух шагов потому, что мы сами обнуляем его при инициализации, для последующих шагов, потому что применяем операцию SHR.
Шаг 2.
Сместим первый бит 6-ти битного байта на вторую позицию, одновременно обнуляя его первый бит. Сместим седьмой байт 8-ми битного байта на 8-е (старшее) место.
Повторяем шаги 1 и 2 шесть раз для каждого бита 6-ти битного байта. Одновременно необходимо следить за тем, что если мы обработали восемь бит байта-источника, следует перейти к новому байту. И последнее - если обработаны все три байта-источники, следует выйти из функции кодирования.
Декодирование
Декодирование ничем не отличается от кодирования. Делаем тоже самое, только источник и приемник меняются местами. Все операции происходят в обратном порядке: проверяем установку первого бита 6-ти битного байта (маску в этом случае надо накладывать с числом 32 (0010000) и в зависимости от результата устанавливаем (или не устанавливаем) младший байт 8-ми битного байта. Затем делаем побитовый сдвиг влево.
После того как мы разобрали весь алгоритм, стоит подготовиться к реализации программы, для этого изобразим блок схемы:
1) Всего алгоритма целиком см. Рисунок.2.4 Блок-схема №1 (Вся программа в целом)
2) Алгоритм кодирования см. Рисунок.2.5 Блок-схема №2 (Кодирование)
3) Алгоритм декодирования см. Рисунок.2.6 Блок-схема №3 (Декодирование)
Рисунок 2.4 Блок-схема №1 (Вся программа в целом)
Рисунок 2.5 Блок-схема №2 (Кодирование)
Рисунок 2.6 Блок-схема №3 (Декодирование)
2.2 Реализация программы
Программа реализована таким образом, что состоит из 3 файлов: kyrsov. cpp;
base64. h;
test. cpp.
Начнем по порядку:
kyrsov. cpp (Созданы функции кодирования/декодирования текста)
Любая программа, написанная на языке программирования С++ прежде всего начинается с подключения заголовочных файлов, или, другими словами, с директив препроцессора (include) Директива препроцессора - это, иными словами, сообщение препроцессору. Нам потребуется всего четыре библиотеки для работы нашей программы
Здесь мы используем директиву include, которая через заголовочные файлы подключает к коду программы необходимые библиотеки. Некоторые заголовочные файлы требуют расширения". h”, так как являются более старыми и унаследовали такой стиль подключения от языка-основателя С.
В представленном в приложении к курсовой работе программном коде, имеют место быть следующие строки:
Листинг 1.
#include "base64. h" // Написанный в ручную заголовочный файл (описан класс string)
#include <iostream> // Стандартная директива потокового ввода/вывода
Операции ввода/вывода выполняются с помощью классов istream (потоковый ввод) и ostream (потоковый вывод). Третий класс, iostream, является производным от них и поддерживает двунаправленный ввод/вывод. Для удобства в библиотеке определены три стандартных объекта-потока:
· cin - объект класса istream, соответствующий стандартному вводу. В общем случае он позволяет читать данные с терминала пользователя;
· cout - объект класса ostream, соответствующий стандартному выводу. В общем случае он позволяет выводить данные на терминал пользователя;
· cerr - объект класса ostream, соответствующий стандартному выводу для ошибок. В этот поток мы направляем сообщения об ошибках программы.
Вывод осуществляется, как правило, с помощью перегруженного оператора сдвига влево (<<), а ввод - с помощью оператора сдвига вправо (>>):
Любая программа также должна содержать функции - основную (обязательно) и дополнительные (опционально). В языке С++ содержится два типа функций:
· Функции возвращающие значение;
· Функции, не возвращающие значений.
Для начала рассмотрим пример функции, которая не возвращает значений.
void /*имя функции*/ (/*параметры функции*/) // заголовок функции
Здесь код начинается с зарезервированного слова void - тип данных, который не хранит какие-либо данные. Этот тип данных показывает, что функция не возвращает никаких значений. void никак по-другому не используется и нужен в программе только для того, чтобы компилятор мог определить тип функции. После зарезервированного слова void пишется имя функции за которым ставятся две круглые скобочки, открывающаяся и закрывающаяся. Если функция будет передавать какие-то данные, то внутри круглых скобочек объявляются параметры функции, которые отделяются друг от друга запятыми. Вся эта строка называется заголовком функции, после которого в функции пишутся две фигурные скобочки - внутри которых будет находится алгоритм, называемый телом функции.
Это был пример дополнительной функции, которая значений не возвращает, так как тип ее данных - void.
Далее мы создаем две функции, для кодирования текста и для декодирования текста: base64_encode и base64_decode соответственно.
Разберем их по отдельности.
Листинг 2.
{
string ret;
int i = 0;
int j = 0;
unsigned char char_array_3 [3];
unsigned char char_array_4 [4];
……………}
Здесь мы описываем переменные.
Класс String
Строки - это объекты, которые представляют
Собой последовательности символов. Стандартный строка класс обеспечивает поддержку таких объектов с интерфейсом, аналогичную стандартные контейнеры однако добавление функции, специально предназначенные для работы со строками символов.
В строка класс-это экземпляр basic_string шаблон класса, который использует char как тип персонажа, его значение по умолчанию char_traits и распределитель типов.
После этого действия, мы циклично модифицируем из 8 битного вида в 6 битный вид нашу строку. Начиная с конца
Листнг 3.
while (in_len--) {
char_array_3 [i++] = * (bytes_to_encode++);
if (i == 3) {
char_array_4 [0] = (char_array_3 [0] & 0xfc) >> 2;
char_array_4 [1] = ( (char_array_3 [0] & 0x03) << 4) + ( (char_array_3 [1] & 0xf0) >> 4);
char_array_4 [2] = ( (char_array_3 [1] & 0x0f) << 2) + ( (char_array_3 [2] & 0xc0) >> 6);
char_array_4 [3] = char_array_3 [2] & 0x3f;
………………………………}
<< - знак побитового смещения
& - знак побитового "и”
Как рассказано и показано в теории здесь специально используется побитовое умножение для проверки установки старших битов с последующим смещение.
Далее повторяется эта же операция, но при условии наличия еще необработанных байтов.
И в конце если не хватает байтов, тоесть их меньше 3, то мы просто заменяем в комбинации отсутствующие байты на знак "=" b дописываем в строку результат.
Листинг 4
while ( (i++ < 3))
ret += '=';
}
Вторая функия base64_decode, ведет и состоит аналогично только что описанной, но с измененным порядком действий (в обратном) и поменянными входом и выходом программы местами.
Теперь рассмотрим заголовочный файл base64. h
В нем описан класс стринг
Листинг 6
#include <string> // заголовочный файл подключающий возможности класса string
using namespace std; // подключаемое пространство имен std
string base64_encode (unsigned char const*, unsigned int len); // инициализация функции base64_encode
string base64_decode (string const& s); // инициализация функции base64_encode
Теперь рассмотрим файл test. cpp (Файл в котором задается). Так же как и в первом файле используются подключаемые модули:
Стандартная библиотека ввода/вывода <iostream> и заголовочный файл "base64. h"
Т.к. в любой программе должна находиться главная функция main, то мы так же ее используем.
При создании нового консольного приложения в языке программирования С++, автоматически создается таккая строка: int main (int argc, char* argv [])
Это - заголовок главной функции main (), в скобочках которого объявлены параметры argс и argv. Так вот, если программу запускать через командную строку, то существует возможность передать какую-либо информацию этой программе через параметры argc и argv []. Первый имеет тип данных int, и содержит количество параметров, передаваемых в функцию main. Причем argc никогда не меньше 1, даже когда мы не передаем никакой информации, потому как первым параметром считается имя функции. Второй параметр - это массив указателей на строки. Через командную строку можно передать только данные строкового типа и именно через параметр argv [] и передается какая-либо информация.
Рисунок 3.1 Схема обьявлений функциив с++
Листинг 7
int main () {
const string s = "kot begit po doroge i myrchit"; // присваиваем переменной s текст который хотели бы закодировать.
string encoded = base64_encode (reinterpret_cast<const unsigned char*> (s. c_str ()), s. length ());
string decoded = base64_decode (encoded);
cout << "encoded: " << encoded << endl;
cout << "decoded: " << decoded << endl;
return 0;
}
В этом коде мы присваиваем переменным string, то значение которое получил функции из файла kyrsov. cpp, при этов в функции мы передаем аргументы
После этого воспользовавшись командой вывода cout. Показываем результат на экран
И после возврщаем 0, командой return 0
Так же здесь присутствуют функции для работы с строковыми данными
s. c_str ()
Возвращает указатель на массив, который содержит null-terminated последовательность символов (т.е., C-string), представляющий текущее значение строка объекта. Этот массив включает в себя одну и ту же последовательность символов, составляющих стоимость строка объект плюс дополнительный завершающий null-символ ('\0') в конце.
s. length ()
Свойство Length возвращает число объектов Char в данном экземпляре, а не число знаков Юникода. Причина в том, что знак Юникода может быть представлен несколькими Char. Для работы с каждым знаком Юникода используйте класс System. Globalization:: StringInfo вместо класса Char.
В некоторых языках, таких как C и C++, знак null указывает на конец строки. На платформе.net Framework знак null может быть внедрен в строку. Когда строка включает один или более знаков null, они могут находиться в любом месте строки. Например, в следующей строке подстроки"abc" и "def" разделены знаком null. Свойство Length возвращает значение 7, показывающее, что строка содержит шесть знаков алфавита и знак null.
2.3 Апробация приложения
В данном разделе находятся тестовые данные и отчет о работе приложения.
Скриншот №1 (Пример работы1)
Скриншот №2 (Пример работы2)
Так как выполнялся запуск программы на операционной системе Linux, то и компилирование и сборка происходит с помощью консоля.
1 команда. g++ - c kyrsov. cpp test. cpp
Команда благодаря ключу - с создает 2 отдельных объектных файла, заголовочный файл base64. h,разместим в том же каталоге, что и другие файлы, его мы подключили внутри программы.
2 команда. g++ kyrsov. o test. o - o result
Команда собирает вместе 2 объектных файла и создает файл запуск для нашей программы.
3 команда. /result
Эта команда запускает нашу программу, после чего мы видим результат на наших скриншотах
На апробации выяснилось, что программа рабочая, выводы верны, декодирование и кодирование прошли успешно
Заключение
В ходе данной курсовой работы были изучены различные аспекты языка программирования С++. Так же был разобран алгоритм кодирования декодирования формата BASE64 и после чего реализован.
Апробация прошла успешно, скриншоты это подтверждают.
Для себя же, я улучшил свои знания и владения языком С++. Конкретно, работа с классом типа string и функциями, разобрался в компиляции и сборке программы состоящей из нескольких файлов. Научился создавать собственные заголовочные файлы и включать их в программу.
Могу сделать окончательный вывод, данная курсовая работа повысила мое знания и я лучше стал разбираться в программировании, так же освоил алгоритм кодирования/декодирования формата BASE64.
Список используемой литературы
1. Холзнер С. Visual C++6: Учебный курс - СПб: Питер, 2001. - 576 с.
2. Андрей Александреску, Современное проектирование на С++ - СПб.: Вильямс, 2002. - 335с.
3. Брюс Эккель, Философия С++ - К.: Питер, 2004, - 575с.
4. Е.Л. Романов, Практикум по программированию на С++. СПб: БВХ-Петербург, 2004. - 425 с.
5. Страуструп Б. Программирование: принципы и практика использования C++, исправленное издание = Programming: Principles and Practice Using C++. - М.: "Вильямс", 2011. - С.1248. - ISBN 978-5-8459-1705-8
6. http://www.cppstudio.com/obuchenie_cpp
7. Р. Лафоре, Объектно-ориентированное программирование в С++. К.: Питер, 2004, - 920с.
8. http://brucha.ru/category/spp/
Приложения
Приложение 1. Программная реализация ультилиты кодирования декодирования формата Base64
1) kyrsov. cpp
#include "base64. h"
#include <iostream>
using namespace std;
// Использование 64 ьитного алфавита base64
static const string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// Проверка вводимых значений
static inline bool is_base64 (unsigned char c) {
return (isalnum (c) || (c == '+') || (c == '/'));
}
// Кодирум наш текст
string base64_encode (unsigned char const* bytes_to_encode, unsigned int in_len) {
string ret;
int i = 0;
int j = 0;
unsigned char char_array_3 [3];
unsigned char char_array_4 [4];
// Запускаем цикл в котором выбираем по 3 байта и модифицируем их, как бы из 8 битного байта в 6 битный байт
while (in_len--) {
char_array_3 [i++] = * (bytes_to_encode++);
if (i == 3) {
char_array_4 [0] = (char_array_3 [0] & 0xfc) >> 2;
char_array_4 [1] = ( (char_array_3 [0] & 0x03) << 4) + ( (char_array_3 [1] & 0xf0) >> 4);
char_array_4 [2] = ( (char_array_3 [1] & 0x0f) << 2) + ( (char_array_3 [2] & 0xc0) >> 6);
char_array_4 [3] = char_array_3 [2] & 0x3f;
// То что получилось запишем в переменную ret
for (i = 0; (i <4); i++)
ret += base64_chars [char_array_4 [i]];
i = 0;
}
}
// Продолжаем, если отправили не пустое сообщение
if (i)
{
for (j = i; j < 3; j++)
// лобавляем в конце ноль, что бы сделать сишную строку
char_array_3 [j] = '\0';
char_array_4 [0] = (char_array_3 [0] & 0xfc) >> 2;
char_array_4 [1] = ( (char_array_3 [0] & 0x03) << 4) + ( (char_array_3 [1] & 0xf0) >> 4);
char_array_4 [2] = ( (char_array_3 [1] & 0x0f) << 2) + ( (char_array_3 [2] & 0xc0) >> 6);
char_array_4 [3] = char_array_3 [2] & 0x3f;
for (j = 0; (j < i + 1); j++)
ret += base64_chars [char_array_4 [j]];
// Если байтов меньше 3, то вместо них записывается знак "=", сколько отсутствует столько и "="
while ( (i++ < 3))
ret += '=';
}
return ret;
}
string base64_decode (string const& encoded_string) {
int in_len = encoded_string. size ();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4 [4], char_array_3 [3];
string ret;
// берем наш закодированную строку, идем обратном порядке и смотрим наличи пустых байтов
while (in_len - && (encoded_string [in_]! = '=') && is_base64 (encoded_string [in_])) {
char_array_4 [i++] = encoded_string [in_]; in_++;
// аналогичные действия в обратном порядке
if (i ==4) {
for (i = 0; i <4; i++)
char_array_4 [i] = base64_chars. find (char_array_4 [i]);
char_array_3 [0] = (char_array_4 [0] << 2) + ( (char_array_4 [1] & 0x30) >> 4);
char_array_3 [1] = ( (char_array_4 [1] & 0xf) << 4) + ( (char_array_4 [2] & 0x3c) >> 2);
char_array_3 [2] = ( (char_array_4 [2] & 0x3) << 6) + char_array_4 [3];
for (i = 0; (i < 3); i++)
ret += char_array_3 [i];
i = 0;
}
}
if (i) {
for (j = i; j <4; j++)
char_array_4 [j] = 0;
for (j = 0; j <4; j++)
char_array_4 [j] = base64_chars. find (char_array_4 [j]);
char_array_3 [0] = (char_array_4 [0] << 2) + ( (char_array_4 [1] & 0x30) >> 4);
char_array_3 [1] = ( (char_array_4 [1] & 0xf) << 4) + ( (char_array_4 [2] & 0x3c) >> 2);
char_array_3 [2] = ( (char_array_4 [2] & 0x3) << 6) + char_array_4 [3];
for (j = 0; (j < i - 1); j++) ret += char_array_3 [j];
}
return ret;
}
2) base64. h
#include <string>
using namespace std;
string base64_encode (unsigned char const*, unsigned int len);
string base64_decode (string const& s);
3) test. cpp
#include "base64. h"
#include <iostream>
using namespace std;
int main () {
const string s = "kot begit po doroge i myrchit";
string encoded = base64_encode (reinterpret_cast<const unsigned char*> (s. c_str ()), s. length ());
string decoded = base64_decode (encoded);
cout << "encoded: " << encoded << endl;
cout << "decoded: " << decoded << endl;
return 0;
}
Программная реализация утилиты кодирования декодирования формата Base64
Размещено на Allbest.ru
Подобные документы
Анализ способов кодирования информации. Разработка устройства кодирования (кодера) информации методом Хемминга. Реализация кодера–декодера на базе ИМС К555ВЖ1. Разработка стенда контроля передаваемой информации, принципиальная схема устройства.
дипломная работа [602,9 K], добавлен 30.08.2010Разработка программы кодирования текстового файла при помощи блочного алгоритма шифрования ТЕА типа "Сеть Фейштеля", который основан на битовых операциях с 64-битным блоком и имеет 128-битный ключ шифрования. Результаты кодирования и декодирования.
лабораторная работа [299,9 K], добавлен 18.07.2013Анализ методов сверточного кодирования. Понятие канала связи и корректирующих кодов, характеристика автомата типа Мура. Особенности сверточного декодирования Витерби. Сущность разработки программного обеспечения системы кодирования сверточным кодом.
дипломная работа [4,9 M], добавлен 11.03.2012Двоичный код, особенности кодирования и декодирования информации. Система счисления как совокупность правил записи чисел с помощью определенного набора символов. Классификация систем счисления, специфика перевода чисел в позиционной системе счисления.
презентация [16,3 K], добавлен 07.06.2011Определение понятий кода, кодирования и декодирования, виды, правила и задачи кодирования. Применение теорем Шеннона в теории связи. Классификация, параметры и построение помехоустойчивых кодов. Методы передачи кодов. Пример построения кода Шеннона.
курсовая работа [212,6 K], добавлен 25.02.2009Методика разработки и механизм отладки программы на языке Лисп, реализующей криптографический алгоритм кодирования информации с открытым ключом – RSA. Математические и алгоритмические основы решения задачи, его программная модель, составление блок-схемы.
курсовая работа [675,7 K], добавлен 20.01.2010Представление информации в двоичной системе. Необходимость кодирования в программировании. Кодирование графической информации, чисел, текста, звука. Разница между кодированием и шифрованием. Двоичное кодирование символьной (текстовой) информации.
реферат [31,7 K], добавлен 27.03.2010Понятие и отличительные черты аналоговой и цифровой информации. Изучение единиц измерения цифровой информации: бит (двоичная цифра) и байт. Особенности передачи, методы кодирования и декодирования текстовой, звуковой и графической цифровой информации.
реферат [479,4 K], добавлен 22.03.2010Разработка алгоритма и программы кодирования и декодирования данных кодом Рида-Малера. Понятие избыточных кодов, их применение. Корелляционный код. Особенности построения простых помехоустойчивых кодов Рида-Маллера. Рассмотрение частных случаев.
курсовая работа [31,9 K], добавлен 09.03.2009Сущность и содержание двоичного кодирования, цели и задачи, этапы реализации данного процесса, оценка его эффективности. Принципы и особенности кодирования чисел и символов, а также рисунков и звука. Используемые методы и приемы, применяемые инструменты.
презентация [756,5 K], добавлен 29.10.2013